@ethlete/query-devtools 1.0.0-next.7 → 1.0.0-next.9

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,12 +1,12 @@
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, reserveOverlayViewportSpace, 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, 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';
4
+ import { queryDevtoolsAbout, hasQueryDevtoolsOverridesAtPath, detectPaginationShape, isDateShapedLeaf, generateQueryDevtoolsStringPreset, generateQueryDevtoolsNumberPreset, collectLeafPaths, armAllQueryDevtoolsMocks, clearQueryDevtoolsArmedMocks, clearQueryDevtoolsFaults, queryDevtoolsResponseHistory, queryDevtoolsMocks, queryDevtoolsAuthSessions, queryDevtoolsArmedMocks, clearQueryDevtoolsAuthSessions, queryDevtoolsApiEnvs, queryDevtoolsApiEnvValues, setQueryDevtoolsApiEnv, queryDevtoolsSettings, setQueryDevtoolsOverridesScope, setQueryDevtoolsArmedMocksScope, setQueryDevtoolsFaultsScope, setQueryDevtoolsSettings, QUERY_DEVTOOLS_TOKEN_TTL_LIMIT, queryDevtoolsApiEnvIsProduction, queryDevtoolsAuthSessionsFor, queryDevtoolsAuthAccountsFor, queryDevtoolsAuthOtherScopeCount, queryDevtoolsAuthActive, queryDevtoolsAuthTabLocal, switchQueryDevtoolsAuthSession, renameQueryDevtoolsAuthSession, forgetQueryDevtoolsAuthSession, logoutQueryDevtoolsAuthSession, setQueryDevtoolsAuthTabLocal, loginQueryDevtoolsAuthAccount, setQueryDevtoolsAuthCredentials, clearQueryDevtoolsAuthCredentials, removeQueryDevtoolsAuthAccount, addQueryDevtoolsAuthAccount, 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, MenuSeparatorComponent, MenuItemShortcutComponent, SelectComponent, SelectSearchDirective, COMPONENTS_VERSION, MenuCheckboxItemComponent, MenuRadioGroupComponent, MenuRadioItemComponent } from '@ethlete/components';
8
+ import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
8
9
  import { HttpHeaders, HttpErrorResponse } from '@angular/common/http';
9
- import { NgTemplateOutlet, DOCUMENT } from '@angular/common';
10
10
  import { QUERY_DEVTOOLS_VIEW_STATE_KEY, queryDevtoolsShortcutLabel, QueryDevtoolsToggleComponent } from '@ethlete/query-devtools/toggle';
11
11
 
12
12
  const groupsOf = () => {
@@ -1343,6 +1343,15 @@ const SCOPE_ROWS = [
1343
1343
  text: 'The next page load starts serving these routes from the panel, before anyone opens it. The bar above the tabs says when they came back.',
1344
1344
  },
1345
1345
  },
1346
+ {
1347
+ key: 'authSessions',
1348
+ label: 'Sessions and accounts',
1349
+ hint: 'The token pairs the Auth tab switches between, and the credentials it logs in with. Local is the default: a vault that forgets the other user on every reload is not one. None keeps nothing at all.',
1350
+ warn: {
1351
+ scopes: ['local'],
1352
+ text: 'Access and refresh tokens for every user you logged in as, plus what you typed into the account fields, stay in this browser until you forget them. They are never kept for an API env marked production.',
1353
+ },
1354
+ },
1346
1355
  {
1347
1356
  key: 'armedFaults',
1348
1357
  label: 'Armed faults',
@@ -1390,6 +1399,7 @@ const LIMIT_ROWS = [
1390
1399
  */
1391
1400
  class QueryDevtoolsSettingsComponent {
1392
1401
  constructor() {
1402
+ this.document = inject(DOCUMENT);
1393
1403
  this.host = injectQueryDevtoolsHost();
1394
1404
  this.SCOPE_ROWS = SCOPE_ROWS;
1395
1405
  this.SCOPES = SCOPES;
@@ -1403,12 +1413,51 @@ class QueryDevtoolsSettingsComponent {
1403
1413
  ...(ngDevMode ? [{ debugName: "responseHistory" }] : /* istanbul ignore next */ []));
1404
1414
  this.mockCount = computed(() => queryDevtoolsMocks().length, /* @ts-ignore */
1405
1415
  ...(ngDevMode ? [{ debugName: "mockCount" }] : /* istanbul ignore next */ []));
1416
+ this.sessionCount = computed(() => queryDevtoolsAuthSessions().length, /* @ts-ignore */
1417
+ ...(ngDevMode ? [{ debugName: "sessionCount" }] : /* istanbul ignore next */ []));
1406
1418
  this.armedMockCount = computed(() => queryDevtoolsArmedMocks().size, /* @ts-ignore */
1407
1419
  ...(ngDevMode ? [{ debugName: "armedMockCount" }] : /* istanbul ignore next */ []));
1408
1420
  this.armedFaultCount = computed(() => this.host.faultClients().filter((client) => client.armed).length, /* @ts-ignore */
1409
1421
  ...(ngDevMode ? [{ debugName: "armedFaultCount" }] : /* istanbul ignore next */ []));
1410
1422
  this.resetConfirming = signal(false, /* @ts-ignore */
1411
1423
  ...(ngDevMode ? [{ debugName: "resetConfirming" }] : /* istanbul ignore next */ []));
1424
+ this.FORGET_SESSIONS = clearQueryDevtoolsAuthSessions;
1425
+ /** The backends the application declared, or an empty list - the card renders only for a declared one. */
1426
+ this.apiEnvs = computed(queryDevtoolsApiEnvs, /* @ts-ignore */
1427
+ ...(ngDevMode ? [{ debugName: "apiEnvs" }] : /* istanbul ignore next */ []));
1428
+ }
1429
+ /** The env id (or typed URL) a switch's key holds, or `null` while the application picks for itself. */
1430
+ apiEnvValue(apiSwitch) {
1431
+ return queryDevtoolsApiEnvValues()[apiSwitch.storageKey] ?? null;
1432
+ }
1433
+ /** The env a switch falls back to with nothing stored, described for the default button. */
1434
+ apiEnvFallback(apiSwitch) {
1435
+ const fallback = apiSwitch.envs.find((env) => env.id === apiSwitch.fallback);
1436
+ return {
1437
+ label: fallback ? `default (${fallback.label ?? fallback.id})` : 'default',
1438
+ production: fallback?.production === true,
1439
+ };
1440
+ }
1441
+ /** Whether the pick in force - the stored env, or the fallback behind an unwritten key - is production. */
1442
+ apiEnvIsProduction(apiSwitch) {
1443
+ const stored = this.apiEnvValue(apiSwitch);
1444
+ return apiSwitch.envs.find((env) => env.id === (stored ?? apiSwitch.fallback))?.production === true;
1445
+ }
1446
+ /** A stored value no declared env carries, which is a URL somebody typed in below. */
1447
+ apiEnvCustomUrl(apiSwitch) {
1448
+ const stored = this.apiEnvValue(apiSwitch);
1449
+ return stored && !apiSwitch.envs.some((env) => env.id === stored) ? stored : '';
1450
+ }
1451
+ /** Writes the application's own key and reloads - it is read before Angular boots, so nothing else can. */
1452
+ pickApiEnv(apiSwitch, value) {
1453
+ setQueryDevtoolsApiEnv(apiSwitch.storageKey, value);
1454
+ this.document.defaultView?.location.reload();
1455
+ }
1456
+ setCustomApiUrl(apiSwitch, value) {
1457
+ const url = value.trim();
1458
+ if (url === this.apiEnvCustomUrl(apiSwitch))
1459
+ return;
1460
+ this.pickApiEnv(apiSwitch, url || null);
1412
1461
  }
1413
1462
  settings() {
1414
1463
  return queryDevtoolsSettings();
@@ -1432,7 +1481,13 @@ class QueryDevtoolsSettingsComponent {
1432
1481
  return setQueryDevtoolsArmedMocksScope(scope);
1433
1482
  if (key === 'armedFaults')
1434
1483
  return setQueryDevtoolsFaultsScope(scope);
1435
- setQueryDevtoolsSettings(key === 'viewState' ? { viewState: scope } : key === 'pins' ? { pins: scope } : { mocks: scope });
1484
+ setQueryDevtoolsSettings(key === 'viewState'
1485
+ ? { viewState: scope }
1486
+ : key === 'pins'
1487
+ ? { pins: scope }
1488
+ : key === 'authSessions'
1489
+ ? { authSessions: scope }
1490
+ : { mocks: scope });
1436
1491
  }
1437
1492
  setLimit(key, value) {
1438
1493
  const count = Number(value);
@@ -1456,11 +1511,11 @@ class QueryDevtoolsSettingsComponent {
1456
1511
  return this.host.queryFacets().has('gone');
1457
1512
  }
1458
1513
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1459
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsSettingsComponent, isStandalone: true, selector: "et-query-devtools-settings", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-settings-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Panel-wide, and kept in <code class=\"et-query-devtools-mono\">localStorage</code> whatever the scopes below say -\n so a scope of <em>none</em> does not erase the choice that set it. Every switch under <strong>Mirrored</strong> is\n the same one its own tab carries.\n </p>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Storage</strong>\n <span class=\"et-query-devtools-count\">what survives a reload, and how long</span>\n </div>\n\n @for (row of SCOPE_ROWS; track row.key) {\n @let scope = scopeOf(row.key);\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ row.label }}</span>\n <div class=\"et-query-devtools-btn-group\">\n @for (option of SCOPES; track option.value) {\n <button\n [class.et-query-devtools-btn--active]=\"scope === option.value\"\n [attr.aria-pressed]=\"scope === option.value\"\n (click)=\"setScope(row.key, option.value)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ option.label }}\n </button>\n }\n <button [title]=\"INDEXED_DB_TITLE\" class=\"et-query-devtools-btn\" type=\"button\" disabled>IndexedDB</button>\n </div>\n </div>\n <small>{{ row.hint }}</small>\n @if (warningFor(row, scope); as warning) {\n <p class=\"et-query-devtools-settings-warn\">\n <span aria-hidden=\"true\">\u26A0</span>\n {{ warning }}\n </p>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Reset devtools</span>\n @if (resetConfirming()) {\n <div class=\"et-query-devtools-btn-group\">\n <button (click)=\"reset()\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Reset everything\n </button>\n <button (click)=\"resetConfirming.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <button (click)=\"resetConfirming.set(true)\" class=\"et-query-devtools-btn\" type=\"button\">Reset\u2026</button>\n }\n </div>\n <small>\n Clears every one of these keys from both stores, whatever the scopes above say, and puts the panel back where it\n ships - layout, filters, selections and pins. The settings on this tab stay.\n </small>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Limits</strong>\n <span class=\"et-query-devtools-count\">how much the panel keeps in memory</span>\n </div>\n\n <div class=\"et-query-devtools-settings-fields\">\n @for (row of LIMIT_ROWS; track row.key) {\n <label class=\"et-query-devtools-field\">\n <span>{{ row.label }}</span>\n <input\n #limitInput\n [value]=\"limitOf(row.key)\"\n [min]=\"row.min\"\n [max]=\"row.max\"\n [step]=\"row.step\"\n (change)=\"setLimit(row.key, limitInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n />\n <small>{{ row.hint }}</small>\n </label>\n }\n\n <label class=\"et-query-devtools-field\">\n <span>Response history</span>\n <input\n #responseHistoryInput\n [value]=\"responseHistory()\"\n (change)=\"setResponseHistory(responseHistoryInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"1\"\n max=\"50\"\n step=\"1\"\n />\n <small>\n bodies each query keeps, which is how far back a response diff can reach. Bodies dominate what the run buffer\n retains, so this is the one limit that costs real memory. Applies to runs from now on.\n @if (settings().responseHistory !== null) {\n <button (click)=\"clearResponseHistory()\" class=\"et-query-devtools-btn\" type=\"button\">\n Back to the app's own value\n </button>\n }\n </small>\n </label>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Mirrored</strong>\n <span class=\"et-query-devtools-count\">the same switches their tabs carry</span>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Queries</span>\n <div class=\"et-query-devtools-btn-group\">\n @let recentFirst = host.queryRecentFirst();\n <button\n [class.et-query-devtools-btn--active]=\"recentFirst\"\n [attr.aria-pressed]=\"recentFirst\"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n recent first\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n tree view\n </button>\n @let gone = showsGoneQueries();\n <button\n [class.et-query-devtools-btn--active]=\"gone\"\n [attr.aria-pressed]=\"gone\"\n (click)=\"toggleGoneQueries()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n list gone\n </button>\n </div>\n </div>\n <small>Sort order, whether the list groups by route path, and whether destroyed queries are listed at all.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Events</span>\n <div class=\"et-query-devtools-settings-controls\">\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-btn--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"host.eventErrorsOnly.set(!errorsOnly)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n failures only\n </button>\n <select\n #eventClientSelect\n [value]=\"host.eventClient() ?? ''\"\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Event log 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 </div>\n </div>\n <small>What the event log is narrowed to. The log itself always holds every client's traffic.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Response overrides</span>\n @let persist = host.overridesPersist();\n <button\n [class.et-query-devtools-btn--active]=\"persist\"\n [attr.aria-pressed]=\"persist\"\n (click)=\"host.toggleOverridesPersist()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Keep across reloads\n </button>\n </div>\n <small>\n The drawer's own toggle, which is the Storage scope above - currently\n <span class=\"et-query-devtools-mono\">{{ host.overridesScopeLabel() }}</span\n >.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Armed</span>\n <div class=\"et-query-devtools-settings-controls\">\n <button\n [disabled]=\"!mockCount() || armedMockCount() === mockCount()\"\n (click)=\"ARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Arm every mock ({{ mockCount() }})\n </button>\n <button\n [disabled]=\"!armedMockCount()\"\n (click)=\"DISARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm mocks ({{ armedMockCount() }})\n </button>\n <button\n [disabled]=\"!armedFaultCount()\"\n (click)=\"DISARM_ALL_FAULTS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm faults ({{ armedFaultCount() }})\n </button>\n </div>\n </div>\n <small>\n The Mocks and Faults tabs' own buttons. Whether either survives a reload is the two <em>Armed</em> scopes above,\n both <em>none</em> unless they were changed.\n </small>\n </div>\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-settings-toolbar{align-items:flex-start}.et-query-devtools-settings-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-settings-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-settings-row-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem}.et-query-devtools-settings-controls{display:flex;align-items:center;gap:.5rem}.et-query-devtools-settings-row>small{display:block;max-inline-size:60rem;margin-block-start:.3rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-warn{margin:.4rem 0 0;color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-settings-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(24rem,1fr));align-items:start;gap:.8rem 1.6rem}.et-query-devtools-settings-fields .et-query-devtools-field{display:grid;grid-template-columns:11rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-settings-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
1514
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsSettingsComponent, isStandalone: true, selector: "et-query-devtools-settings", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-settings-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Panel-wide, and kept in <code class=\"et-query-devtools-mono\">localStorage</code> whatever the scopes below say -\n so a scope of <em>none</em> does not erase the choice that set it. Every switch under <strong>Mirrored</strong> is\n the same one its own tab carries.\n </p>\n </div>\n\n @if (apiEnvs().length) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>API environment</strong>\n <span class=\"et-query-devtools-count\">the app's own key, not the panel's - picking reloads the page</span>\n </div>\n\n @for (apiSwitch of apiEnvs(); track apiSwitch.storageKey) {\n @let value = apiEnvValue(apiSwitch);\n @let custom = apiEnvCustomUrl(apiSwitch);\n @let fallback = apiEnvFallback(apiSwitch);\n @let production = apiEnvIsProduction(apiSwitch);\n <div [class.et-query-devtools-settings-api--production]=\"production\" class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ apiSwitch.name }}</span>\n <div class=\"et-query-devtools-btn-group\">\n <button\n [class.et-query-devtools-btn--active]=\"value === null\"\n [class.et-query-devtools-btn--danger]=\"fallback.production\"\n [attr.aria-pressed]=\"value === null\"\n (click)=\"pickApiEnv(apiSwitch, null)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ fallback.production ? '\u26A0 ' : '' }}{{ fallback.label }}\n </button>\n @for (env of apiSwitch.envs; track env.id) {\n <button\n [class.et-query-devtools-btn--active]=\"value === env.id\"\n [class.et-query-devtools-btn--danger]=\"env.production\"\n [attr.aria-pressed]=\"value === env.id\"\n [title]=\"env.url ?? env.id\"\n (click)=\"pickApiEnv(apiSwitch, env.id)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ env.production ? '\u26A0 ' : '' }}{{ env.label ?? env.id }}\n </button>\n }\n </div>\n </div>\n @if (production) {\n <strong class=\"et-query-devtools-settings-api-live\">\n Live data. Every request from this app reaches the real backend.\n </strong>\n }\n <small>\n Written to <span class=\"et-query-devtools-mono\">{{ apiSwitch.storageKey }}</span> in\n <code class=\"et-query-devtools-mono\">localStorage</code>, which the app reads before it boots.\n </small>\n @if (apiSwitch.custom) {\n <label class=\"et-query-devtools-field et-query-devtools-settings-api-url\">\n <span>Base URL</span>\n <input\n #customUrlInput\n [value]=\"custom\"\n (change)=\"setCustomApiUrl(apiSwitch, customUrlInput.value)\"\n class=\"et-query-devtools-input\"\n type=\"url\"\n placeholder=\"http://localhost:8040\"\n />\n <small>A backend none of the buttons above name. Empty goes back to the app's own default.</small>\n </label>\n }\n </div>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Storage</strong>\n <span class=\"et-query-devtools-count\">what survives a reload, and how long</span>\n </div>\n\n @for (row of SCOPE_ROWS; track row.key) {\n @let scope = scopeOf(row.key);\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ row.label }}</span>\n <div class=\"et-query-devtools-btn-group\">\n @for (option of SCOPES; track option.value) {\n <button\n [class.et-query-devtools-btn--active]=\"scope === option.value\"\n [attr.aria-pressed]=\"scope === option.value\"\n (click)=\"setScope(row.key, option.value)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ option.label }}\n </button>\n }\n <button [title]=\"INDEXED_DB_TITLE\" class=\"et-query-devtools-btn\" type=\"button\" disabled>IndexedDB</button>\n </div>\n </div>\n <small>{{ row.hint }}</small>\n @if (warningFor(row, scope); as warning) {\n <p class=\"et-query-devtools-settings-warn\">\n <span aria-hidden=\"true\">\u26A0</span>\n {{ warning }}\n </p>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Reset devtools</span>\n @if (resetConfirming()) {\n <div class=\"et-query-devtools-btn-group\">\n <button (click)=\"reset()\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Reset everything\n </button>\n <button (click)=\"resetConfirming.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <button (click)=\"resetConfirming.set(true)\" class=\"et-query-devtools-btn\" type=\"button\">Reset\u2026</button>\n }\n </div>\n <small>\n Clears every one of these keys from both stores, whatever the scopes above say, and puts the panel back where it\n ships - layout, filters, selections and pins. The settings on this tab stay.\n </small>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Limits</strong>\n <span class=\"et-query-devtools-count\">how much the panel keeps in memory</span>\n </div>\n\n <div class=\"et-query-devtools-settings-fields\">\n @for (row of LIMIT_ROWS; track row.key) {\n <label class=\"et-query-devtools-field\">\n <span>{{ row.label }}</span>\n <input\n #limitInput\n [value]=\"limitOf(row.key)\"\n [min]=\"row.min\"\n [max]=\"row.max\"\n [step]=\"row.step\"\n (change)=\"setLimit(row.key, limitInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n />\n <small>{{ row.hint }}</small>\n </label>\n }\n\n <label class=\"et-query-devtools-field\">\n <span>Response history</span>\n <input\n #responseHistoryInput\n [value]=\"responseHistory()\"\n (change)=\"setResponseHistory(responseHistoryInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"1\"\n max=\"50\"\n step=\"1\"\n />\n <small>\n bodies each query keeps, which is how far back a response diff can reach. Bodies dominate what the run buffer\n retains, so this is the one limit that costs real memory. Applies to runs from now on.\n @if (settings().responseHistory !== null) {\n <button (click)=\"clearResponseHistory()\" class=\"et-query-devtools-btn\" type=\"button\">\n Back to the app's own value\n </button>\n }\n </small>\n </label>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Mirrored</strong>\n <span class=\"et-query-devtools-count\">the same switches their tabs carry</span>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Queries</span>\n <div class=\"et-query-devtools-btn-group\">\n @let recentFirst = host.queryRecentFirst();\n <button\n [class.et-query-devtools-btn--active]=\"recentFirst\"\n [attr.aria-pressed]=\"recentFirst\"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n recent first\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n tree view\n </button>\n @let gone = showsGoneQueries();\n <button\n [class.et-query-devtools-btn--active]=\"gone\"\n [attr.aria-pressed]=\"gone\"\n (click)=\"toggleGoneQueries()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n list gone\n </button>\n </div>\n </div>\n <small>Sort order, whether the list groups by route path, and whether destroyed queries are listed at all.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Sessions</span>\n <div class=\"et-query-devtools-settings-controls\">\n <span class=\"et-query-devtools-count\">{{ sessionCount() }} stored</span>\n <button\n [disabled]=\"!sessionCount()\"\n (click)=\"FORGET_SESSIONS()\"\n class=\"et-query-devtools-btn et-query-devtools-btn--danger\"\n type=\"button\"\n >\n Forget every session\n </button>\n </div>\n </div>\n <small>\n Drops every stored token pair, every account added in the panel and every credential typed into one. The live\n session is left alone - the Auth tab switches between them.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Events</span>\n <div class=\"et-query-devtools-settings-controls\">\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-btn--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"host.eventErrorsOnly.set(!errorsOnly)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n failures only\n </button>\n <select\n #eventClientSelect\n [value]=\"host.eventClient() ?? ''\"\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Event log 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 </div>\n </div>\n <small>What the event log is narrowed to. The log itself always holds every client's traffic.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Response overrides</span>\n @let persist = host.overridesPersist();\n <button\n [class.et-query-devtools-btn--active]=\"persist\"\n [attr.aria-pressed]=\"persist\"\n (click)=\"host.toggleOverridesPersist()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Keep across reloads\n </button>\n </div>\n <small>\n The drawer's own toggle, which is the Storage scope above - currently\n <span class=\"et-query-devtools-mono\">{{ host.overridesScopeLabel() }}</span\n >.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Armed</span>\n <div class=\"et-query-devtools-settings-controls\">\n <button\n [disabled]=\"!mockCount() || armedMockCount() === mockCount()\"\n (click)=\"ARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Arm every mock ({{ mockCount() }})\n </button>\n <button\n [disabled]=\"!armedMockCount()\"\n (click)=\"DISARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm mocks ({{ armedMockCount() }})\n </button>\n <button\n [disabled]=\"!armedFaultCount()\"\n (click)=\"DISARM_ALL_FAULTS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm faults ({{ armedFaultCount() }})\n </button>\n </div>\n </div>\n <small>\n The Mocks and Faults tabs' own buttons. Whether either survives a reload is the two <em>Armed</em> scopes above,\n both <em>none</em> unless they were changed.\n </small>\n </div>\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-settings-toolbar{align-items:flex-start}.et-query-devtools-settings-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-settings-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-settings-row-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem}.et-query-devtools-settings-controls{display:flex;align-items:center;gap:.5rem}.et-query-devtools-settings-row>small{display:block;max-inline-size:60rem;margin-block-start:.3rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-warn{margin:.4rem 0 0;color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-settings-api--production{background:color-mix(in srgb,var(--_et-qdt-error) 8%,transparent);box-shadow:inset .3rem 0 0 var(--_et-qdt-error);padding-inline-start:.8rem}.et-query-devtools-settings-api-live{display:block;margin-block-start:.4rem;color:var(--_et-qdt-error);font-size:1.2rem}.et-query-devtools-btn--danger.et-query-devtools-btn--active{background:color-mix(in srgb,var(--_et-qdt-error) 28%,transparent);border-color:var(--_et-qdt-error);color:#fff}@media(prefers-reduced-motion:no-preference){.et-query-devtools-settings-api-live{animation:et-qdt-settings-api-live 1.6s ease-in-out infinite}}@keyframes et-qdt-settings-api-live{0%,to{opacity:.55}50%{opacity:1}}.et-query-devtools-settings-api-url{display:grid;grid-template-columns:auto minmax(0,32rem);align-items:center;margin-block-start:.5rem;gap:.2rem .6rem}.et-query-devtools-settings-api-url small{grid-column:1 / -1;max-inline-size:60rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(24rem,1fr));align-items:start;gap:.8rem 1.6rem}.et-query-devtools-settings-fields .et-query-devtools-field{display:grid;grid-template-columns:11rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-settings-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
1460
1515
  }
1461
1516
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsSettingsComponent, decorators: [{
1462
1517
  type: Component,
1463
- args: [{ selector: 'et-query-devtools-settings', encapsulation: ViewEncapsulation.None, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-settings-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Panel-wide, and kept in <code class=\"et-query-devtools-mono\">localStorage</code> whatever the scopes below say -\n so a scope of <em>none</em> does not erase the choice that set it. Every switch under <strong>Mirrored</strong> is\n the same one its own tab carries.\n </p>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Storage</strong>\n <span class=\"et-query-devtools-count\">what survives a reload, and how long</span>\n </div>\n\n @for (row of SCOPE_ROWS; track row.key) {\n @let scope = scopeOf(row.key);\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ row.label }}</span>\n <div class=\"et-query-devtools-btn-group\">\n @for (option of SCOPES; track option.value) {\n <button\n [class.et-query-devtools-btn--active]=\"scope === option.value\"\n [attr.aria-pressed]=\"scope === option.value\"\n (click)=\"setScope(row.key, option.value)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ option.label }}\n </button>\n }\n <button [title]=\"INDEXED_DB_TITLE\" class=\"et-query-devtools-btn\" type=\"button\" disabled>IndexedDB</button>\n </div>\n </div>\n <small>{{ row.hint }}</small>\n @if (warningFor(row, scope); as warning) {\n <p class=\"et-query-devtools-settings-warn\">\n <span aria-hidden=\"true\">\u26A0</span>\n {{ warning }}\n </p>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Reset devtools</span>\n @if (resetConfirming()) {\n <div class=\"et-query-devtools-btn-group\">\n <button (click)=\"reset()\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Reset everything\n </button>\n <button (click)=\"resetConfirming.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <button (click)=\"resetConfirming.set(true)\" class=\"et-query-devtools-btn\" type=\"button\">Reset\u2026</button>\n }\n </div>\n <small>\n Clears every one of these keys from both stores, whatever the scopes above say, and puts the panel back where it\n ships - layout, filters, selections and pins. The settings on this tab stay.\n </small>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Limits</strong>\n <span class=\"et-query-devtools-count\">how much the panel keeps in memory</span>\n </div>\n\n <div class=\"et-query-devtools-settings-fields\">\n @for (row of LIMIT_ROWS; track row.key) {\n <label class=\"et-query-devtools-field\">\n <span>{{ row.label }}</span>\n <input\n #limitInput\n [value]=\"limitOf(row.key)\"\n [min]=\"row.min\"\n [max]=\"row.max\"\n [step]=\"row.step\"\n (change)=\"setLimit(row.key, limitInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n />\n <small>{{ row.hint }}</small>\n </label>\n }\n\n <label class=\"et-query-devtools-field\">\n <span>Response history</span>\n <input\n #responseHistoryInput\n [value]=\"responseHistory()\"\n (change)=\"setResponseHistory(responseHistoryInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"1\"\n max=\"50\"\n step=\"1\"\n />\n <small>\n bodies each query keeps, which is how far back a response diff can reach. Bodies dominate what the run buffer\n retains, so this is the one limit that costs real memory. Applies to runs from now on.\n @if (settings().responseHistory !== null) {\n <button (click)=\"clearResponseHistory()\" class=\"et-query-devtools-btn\" type=\"button\">\n Back to the app's own value\n </button>\n }\n </small>\n </label>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Mirrored</strong>\n <span class=\"et-query-devtools-count\">the same switches their tabs carry</span>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Queries</span>\n <div class=\"et-query-devtools-btn-group\">\n @let recentFirst = host.queryRecentFirst();\n <button\n [class.et-query-devtools-btn--active]=\"recentFirst\"\n [attr.aria-pressed]=\"recentFirst\"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n recent first\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n tree view\n </button>\n @let gone = showsGoneQueries();\n <button\n [class.et-query-devtools-btn--active]=\"gone\"\n [attr.aria-pressed]=\"gone\"\n (click)=\"toggleGoneQueries()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n list gone\n </button>\n </div>\n </div>\n <small>Sort order, whether the list groups by route path, and whether destroyed queries are listed at all.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Events</span>\n <div class=\"et-query-devtools-settings-controls\">\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-btn--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"host.eventErrorsOnly.set(!errorsOnly)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n failures only\n </button>\n <select\n #eventClientSelect\n [value]=\"host.eventClient() ?? ''\"\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Event log 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 </div>\n </div>\n <small>What the event log is narrowed to. The log itself always holds every client's traffic.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Response overrides</span>\n @let persist = host.overridesPersist();\n <button\n [class.et-query-devtools-btn--active]=\"persist\"\n [attr.aria-pressed]=\"persist\"\n (click)=\"host.toggleOverridesPersist()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Keep across reloads\n </button>\n </div>\n <small>\n The drawer's own toggle, which is the Storage scope above - currently\n <span class=\"et-query-devtools-mono\">{{ host.overridesScopeLabel() }}</span\n >.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Armed</span>\n <div class=\"et-query-devtools-settings-controls\">\n <button\n [disabled]=\"!mockCount() || armedMockCount() === mockCount()\"\n (click)=\"ARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Arm every mock ({{ mockCount() }})\n </button>\n <button\n [disabled]=\"!armedMockCount()\"\n (click)=\"DISARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm mocks ({{ armedMockCount() }})\n </button>\n <button\n [disabled]=\"!armedFaultCount()\"\n (click)=\"DISARM_ALL_FAULTS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm faults ({{ armedFaultCount() }})\n </button>\n </div>\n </div>\n <small>\n The Mocks and Faults tabs' own buttons. Whether either survives a reload is the two <em>Armed</em> scopes above,\n both <em>none</em> unless they were changed.\n </small>\n </div>\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-settings-toolbar{align-items:flex-start}.et-query-devtools-settings-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-settings-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-settings-row-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem}.et-query-devtools-settings-controls{display:flex;align-items:center;gap:.5rem}.et-query-devtools-settings-row>small{display:block;max-inline-size:60rem;margin-block-start:.3rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-warn{margin:.4rem 0 0;color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-settings-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(24rem,1fr));align-items:start;gap:.8rem 1.6rem}.et-query-devtools-settings-fields .et-query-devtools-field{display:grid;grid-template-columns:11rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-settings-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"] }]
1518
+ args: [{ selector: 'et-query-devtools-settings', encapsulation: ViewEncapsulation.None, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-settings-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Panel-wide, and kept in <code class=\"et-query-devtools-mono\">localStorage</code> whatever the scopes below say -\n so a scope of <em>none</em> does not erase the choice that set it. Every switch under <strong>Mirrored</strong> is\n the same one its own tab carries.\n </p>\n </div>\n\n @if (apiEnvs().length) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>API environment</strong>\n <span class=\"et-query-devtools-count\">the app's own key, not the panel's - picking reloads the page</span>\n </div>\n\n @for (apiSwitch of apiEnvs(); track apiSwitch.storageKey) {\n @let value = apiEnvValue(apiSwitch);\n @let custom = apiEnvCustomUrl(apiSwitch);\n @let fallback = apiEnvFallback(apiSwitch);\n @let production = apiEnvIsProduction(apiSwitch);\n <div [class.et-query-devtools-settings-api--production]=\"production\" class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ apiSwitch.name }}</span>\n <div class=\"et-query-devtools-btn-group\">\n <button\n [class.et-query-devtools-btn--active]=\"value === null\"\n [class.et-query-devtools-btn--danger]=\"fallback.production\"\n [attr.aria-pressed]=\"value === null\"\n (click)=\"pickApiEnv(apiSwitch, null)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ fallback.production ? '\u26A0 ' : '' }}{{ fallback.label }}\n </button>\n @for (env of apiSwitch.envs; track env.id) {\n <button\n [class.et-query-devtools-btn--active]=\"value === env.id\"\n [class.et-query-devtools-btn--danger]=\"env.production\"\n [attr.aria-pressed]=\"value === env.id\"\n [title]=\"env.url ?? env.id\"\n (click)=\"pickApiEnv(apiSwitch, env.id)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ env.production ? '\u26A0 ' : '' }}{{ env.label ?? env.id }}\n </button>\n }\n </div>\n </div>\n @if (production) {\n <strong class=\"et-query-devtools-settings-api-live\">\n Live data. Every request from this app reaches the real backend.\n </strong>\n }\n <small>\n Written to <span class=\"et-query-devtools-mono\">{{ apiSwitch.storageKey }}</span> in\n <code class=\"et-query-devtools-mono\">localStorage</code>, which the app reads before it boots.\n </small>\n @if (apiSwitch.custom) {\n <label class=\"et-query-devtools-field et-query-devtools-settings-api-url\">\n <span>Base URL</span>\n <input\n #customUrlInput\n [value]=\"custom\"\n (change)=\"setCustomApiUrl(apiSwitch, customUrlInput.value)\"\n class=\"et-query-devtools-input\"\n type=\"url\"\n placeholder=\"http://localhost:8040\"\n />\n <small>A backend none of the buttons above name. Empty goes back to the app's own default.</small>\n </label>\n }\n </div>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Storage</strong>\n <span class=\"et-query-devtools-count\">what survives a reload, and how long</span>\n </div>\n\n @for (row of SCOPE_ROWS; track row.key) {\n @let scope = scopeOf(row.key);\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>{{ row.label }}</span>\n <div class=\"et-query-devtools-btn-group\">\n @for (option of SCOPES; track option.value) {\n <button\n [class.et-query-devtools-btn--active]=\"scope === option.value\"\n [attr.aria-pressed]=\"scope === option.value\"\n (click)=\"setScope(row.key, option.value)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ option.label }}\n </button>\n }\n <button [title]=\"INDEXED_DB_TITLE\" class=\"et-query-devtools-btn\" type=\"button\" disabled>IndexedDB</button>\n </div>\n </div>\n <small>{{ row.hint }}</small>\n @if (warningFor(row, scope); as warning) {\n <p class=\"et-query-devtools-settings-warn\">\n <span aria-hidden=\"true\">\u26A0</span>\n {{ warning }}\n </p>\n }\n </div>\n }\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Reset devtools</span>\n @if (resetConfirming()) {\n <div class=\"et-query-devtools-btn-group\">\n <button (click)=\"reset()\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Reset everything\n </button>\n <button (click)=\"resetConfirming.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <button (click)=\"resetConfirming.set(true)\" class=\"et-query-devtools-btn\" type=\"button\">Reset\u2026</button>\n }\n </div>\n <small>\n Clears every one of these keys from both stores, whatever the scopes above say, and puts the panel back where it\n ships - layout, filters, selections and pins. The settings on this tab stay.\n </small>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Limits</strong>\n <span class=\"et-query-devtools-count\">how much the panel keeps in memory</span>\n </div>\n\n <div class=\"et-query-devtools-settings-fields\">\n @for (row of LIMIT_ROWS; track row.key) {\n <label class=\"et-query-devtools-field\">\n <span>{{ row.label }}</span>\n <input\n #limitInput\n [value]=\"limitOf(row.key)\"\n [min]=\"row.min\"\n [max]=\"row.max\"\n [step]=\"row.step\"\n (change)=\"setLimit(row.key, limitInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n />\n <small>{{ row.hint }}</small>\n </label>\n }\n\n <label class=\"et-query-devtools-field\">\n <span>Response history</span>\n <input\n #responseHistoryInput\n [value]=\"responseHistory()\"\n (change)=\"setResponseHistory(responseHistoryInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"1\"\n max=\"50\"\n step=\"1\"\n />\n <small>\n bodies each query keeps, which is how far back a response diff can reach. Bodies dominate what the run buffer\n retains, so this is the one limit that costs real memory. Applies to runs from now on.\n @if (settings().responseHistory !== null) {\n <button (click)=\"clearResponseHistory()\" class=\"et-query-devtools-btn\" type=\"button\">\n Back to the app's own value\n </button>\n }\n </small>\n </label>\n </div>\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Mirrored</strong>\n <span class=\"et-query-devtools-count\">the same switches their tabs carry</span>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Queries</span>\n <div class=\"et-query-devtools-btn-group\">\n @let recentFirst = host.queryRecentFirst();\n <button\n [class.et-query-devtools-btn--active]=\"recentFirst\"\n [attr.aria-pressed]=\"recentFirst\"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n recent first\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n tree view\n </button>\n @let gone = showsGoneQueries();\n <button\n [class.et-query-devtools-btn--active]=\"gone\"\n [attr.aria-pressed]=\"gone\"\n (click)=\"toggleGoneQueries()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n list gone\n </button>\n </div>\n </div>\n <small>Sort order, whether the list groups by route path, and whether destroyed queries are listed at all.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Sessions</span>\n <div class=\"et-query-devtools-settings-controls\">\n <span class=\"et-query-devtools-count\">{{ sessionCount() }} stored</span>\n <button\n [disabled]=\"!sessionCount()\"\n (click)=\"FORGET_SESSIONS()\"\n class=\"et-query-devtools-btn et-query-devtools-btn--danger\"\n type=\"button\"\n >\n Forget every session\n </button>\n </div>\n </div>\n <small>\n Drops every stored token pair, every account added in the panel and every credential typed into one. The live\n session is left alone - the Auth tab switches between them.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Events</span>\n <div class=\"et-query-devtools-settings-controls\">\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-btn--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"host.eventErrorsOnly.set(!errorsOnly)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n failures only\n </button>\n <select\n #eventClientSelect\n [value]=\"host.eventClient() ?? ''\"\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Event log 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 </div>\n </div>\n <small>What the event log is narrowed to. The log itself always holds every client's traffic.</small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Response overrides</span>\n @let persist = host.overridesPersist();\n <button\n [class.et-query-devtools-btn--active]=\"persist\"\n [attr.aria-pressed]=\"persist\"\n (click)=\"host.toggleOverridesPersist()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Keep across reloads\n </button>\n </div>\n <small>\n The drawer's own toggle, which is the Storage scope above - currently\n <span class=\"et-query-devtools-mono\">{{ host.overridesScopeLabel() }}</span\n >.\n </small>\n </div>\n\n <div class=\"et-query-devtools-settings-row\">\n <div class=\"et-query-devtools-settings-row-head\">\n <span>Armed</span>\n <div class=\"et-query-devtools-settings-controls\">\n <button\n [disabled]=\"!mockCount() || armedMockCount() === mockCount()\"\n (click)=\"ARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Arm every mock ({{ mockCount() }})\n </button>\n <button\n [disabled]=\"!armedMockCount()\"\n (click)=\"DISARM_ALL_MOCKS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm mocks ({{ armedMockCount() }})\n </button>\n <button\n [disabled]=\"!armedFaultCount()\"\n (click)=\"DISARM_ALL_FAULTS()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Disarm faults ({{ armedFaultCount() }})\n </button>\n </div>\n </div>\n <small>\n The Mocks and Faults tabs' own buttons. Whether either survives a reload is the two <em>Armed</em> scopes above,\n both <em>none</em> unless they were changed.\n </small>\n </div>\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-settings-toolbar{align-items:flex-start}.et-query-devtools-settings-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-settings-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-settings-row-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem}.et-query-devtools-settings-controls{display:flex;align-items:center;gap:.5rem}.et-query-devtools-settings-row>small{display:block;max-inline-size:60rem;margin-block-start:.3rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-warn{margin:.4rem 0 0;color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-settings-api--production{background:color-mix(in srgb,var(--_et-qdt-error) 8%,transparent);box-shadow:inset .3rem 0 0 var(--_et-qdt-error);padding-inline-start:.8rem}.et-query-devtools-settings-api-live{display:block;margin-block-start:.4rem;color:var(--_et-qdt-error);font-size:1.2rem}.et-query-devtools-btn--danger.et-query-devtools-btn--active{background:color-mix(in srgb,var(--_et-qdt-error) 28%,transparent);border-color:var(--_et-qdt-error);color:#fff}@media(prefers-reduced-motion:no-preference){.et-query-devtools-settings-api-live{animation:et-qdt-settings-api-live 1.6s ease-in-out infinite}}@keyframes et-qdt-settings-api-live{0%,to{opacity:.55}50%{opacity:1}}.et-query-devtools-settings-api-url{display:grid;grid-template-columns:auto minmax(0,32rem);align-items:center;margin-block-start:.5rem;gap:.2rem .6rem}.et-query-devtools-settings-api-url small{grid-column:1 / -1;max-inline-size:60rem;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-settings-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(24rem,1fr));align-items:start;gap:.8rem 1.6rem}.et-query-devtools-settings-fields .et-query-devtools-field{display:grid;grid-template-columns:11rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-settings-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"] }]
1464
1519
  }] });
1465
1520
 
1466
1521
  /**
@@ -1534,11 +1589,127 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
1534
1589
  `, encapsulation: ViewEncapsulation.None, styles: ["@layer components{.et-query-devtools-features{display:grid;grid-template-columns:max-content minmax(0,1fr);align-items:center;gap:.4rem .8rem;margin:0 0 .8rem;padding:0;list-style:none}.et-query-devtools-meta .et-query-devtools-features{margin-block-end:0}.et-query-devtools-feature{display:contents}.et-query-devtools-feature>.et-query-devtools-chip{justify-self:start}.et-query-devtools-feature-details{display:flex;flex-wrap:wrap;gap:.3rem}.et-query-devtools-feature-detail{display:inline-flex;align-items:baseline;gap:.4rem;padding:.2rem .5rem;border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent);font-size:1.1rem}.et-query-devtools-feature-label{color:var(--_et-qdt-muted)}.et-query-devtools-feature-value{color:var(--_et-qdt-fg);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}}\n"] }]
1535
1590
  }], propDecorators: { features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", required: true }] }] } });
1536
1591
 
1537
- /** The Auth tab: registered bearer auth providers, their tokens and their internal queries. */
1592
+ /** The Auth tab: registered bearer auth providers, their tokens, their sessions and their queries. */
1538
1593
  class QueryDevtoolsAuthTabComponent {
1539
1594
  constructor() {
1540
1595
  this.host = injectQueryDevtoolsHost();
1541
1596
  this.TTL_LIMIT = QUERY_DEVTOOLS_TOKEN_TTL_LIMIT;
1597
+ /** Which accounts show their credential fields. Nothing is expanded until somebody asks for it. */
1598
+ this.expandedAccounts = signal(new Set(), /* @ts-ignore */
1599
+ ...(ngDevMode ? [{ debugName: "expandedAccounts" }] : /* istanbul ignore next */ []));
1600
+ /** Which providers show the form that adds an account of your own. */
1601
+ this.addingFor = signal(new Set(), /* @ts-ignore */
1602
+ ...(ngDevMode ? [{ debugName: "addingFor" }] : /* istanbul ignore next */ []));
1603
+ /** Nothing of a real user is ever kept on this machine, so the whole vault stands down. */
1604
+ this.production = computed(queryDevtoolsApiEnvIsProduction, /* @ts-ignore */
1605
+ ...(ngDevMode ? [{ debugName: "production" }] : /* istanbul ignore next */ []));
1606
+ this.reloadOnSwitch = computed(() => queryDevtoolsSettings().reloadOnAuthSwitch, /* @ts-ignore */
1607
+ ...(ngDevMode ? [{ debugName: "reloadOnSwitch" }] : /* istanbul ignore next */ []));
1608
+ }
1609
+ sessionsFor(entry) {
1610
+ return queryDevtoolsAuthSessionsFor(this.providerName(entry));
1611
+ }
1612
+ accountsFor(entry) {
1613
+ return queryDevtoolsAuthAccountsFor(this.providerName(entry));
1614
+ }
1615
+ /** How many sessions this provider holds for a backend the app is not pointed at now. */
1616
+ otherScopeCount(entry) {
1617
+ return queryDevtoolsAuthOtherScopeCount(this.providerName(entry));
1618
+ }
1619
+ isActive(entry, session) {
1620
+ return queryDevtoolsAuthActive()[this.providerName(entry)] === session.id;
1621
+ }
1622
+ isTabLocal(entry) {
1623
+ return queryDevtoolsAuthTabLocal()[this.providerName(entry)] === true;
1624
+ }
1625
+ /** The provider's own auth queries, which is what an account added here can log in through. */
1626
+ loginQueries(entry) {
1627
+ return (entry.meta.authQueries ?? []).filter((query) => query.kind === 'auth').map((query) => query.key);
1628
+ }
1629
+ sessionAge(session) {
1630
+ if (!session.savedAt)
1631
+ return 'unknown';
1632
+ const seconds = Math.round((Date.now() - session.savedAt) / 1000);
1633
+ if (seconds < 60)
1634
+ return `${seconds}s ago`;
1635
+ if (seconds < 3600)
1636
+ return `${Math.round(seconds / 60)}m ago`;
1637
+ return `${Math.round(seconds / 3600)}h ago`;
1638
+ }
1639
+ /** Whether the stored access token is spent, which the refresh token is expected to fix on a switch. */
1640
+ isExpired(session) {
1641
+ return session.expiresAt !== null && session.expiresAt * 1000 < Date.now();
1642
+ }
1643
+ switchTo(session) {
1644
+ switchQueryDevtoolsAuthSession({ sessionId: session.id });
1645
+ }
1646
+ rename(session, label) {
1647
+ if (label.trim() === '' || label === session.label)
1648
+ return;
1649
+ renameQueryDevtoolsAuthSession({ sessionId: session.id, label: label.trim() });
1650
+ }
1651
+ forget(session) {
1652
+ forgetQueryDevtoolsAuthSession(session.id);
1653
+ }
1654
+ logout(entry) {
1655
+ logoutQueryDevtoolsAuthSession(this.providerName(entry));
1656
+ }
1657
+ toggleTabLocal(entry) {
1658
+ setQueryDevtoolsAuthTabLocal({ provider: this.providerName(entry), tabLocal: !this.isTabLocal(entry) });
1659
+ }
1660
+ login(account) {
1661
+ loginQueryDevtoolsAuthAccount(account.id);
1662
+ }
1663
+ isExpanded(account) {
1664
+ return this.expandedAccounts().has(account.id);
1665
+ }
1666
+ toggleCredentials(account) {
1667
+ this.expandedAccounts.update((current) => {
1668
+ const next = new Set(current);
1669
+ if (!next.delete(account.id))
1670
+ next.add(account.id);
1671
+ return next;
1672
+ });
1673
+ }
1674
+ /** One field at a time, merged into what is already there - a login form here would be a form to build. */
1675
+ setCredential(options) {
1676
+ const { account, field, value } = options;
1677
+ setQueryDevtoolsAuthCredentials({ accountId: account.id, values: { ...account.values, [field]: value } });
1678
+ }
1679
+ clearCredentials(account) {
1680
+ clearQueryDevtoolsAuthCredentials(account.id);
1681
+ }
1682
+ removeAccount(account) {
1683
+ removeQueryDevtoolsAuthAccount(account.id);
1684
+ }
1685
+ isAdding(entry) {
1686
+ return this.addingFor().has(this.providerName(entry));
1687
+ }
1688
+ toggleAdding(entry) {
1689
+ this.addingFor.update((current) => {
1690
+ const next = new Set(current);
1691
+ if (!next.delete(this.providerName(entry)))
1692
+ next.add(this.providerName(entry));
1693
+ return next;
1694
+ });
1695
+ }
1696
+ addAccount(options) {
1697
+ const { entry, label, loginQuery } = options;
1698
+ if (label.trim() === '' || !loginQuery)
1699
+ return;
1700
+ const id = addQueryDevtoolsAuthAccount({
1701
+ provider: this.providerName(entry),
1702
+ label: label.trim(),
1703
+ loginQuery,
1704
+ });
1705
+ this.expandedAccounts.update((current) => new Set(current).add(id));
1706
+ this.toggleAdding(entry);
1707
+ }
1708
+ setReloadOnSwitch(reload) {
1709
+ setQueryDevtoolsSettings({ reloadOnAuthSwitch: reload });
1710
+ }
1711
+ forgetEverything() {
1712
+ clearQueryDevtoolsAuthSessions();
1542
1713
  }
1543
1714
  /**
1544
1715
  * Arms an access-token lifetime from the input's raw value, or disarms on an empty one. Clamped here
@@ -1563,11 +1734,11 @@ class QueryDevtoolsAuthTabComponent {
1563
1734
  return entry.meta.name ?? '';
1564
1735
  }
1565
1736
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsAuthTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1566
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsAuthTabComponent, isStandalone: true, selector: "et-query-devtools-auth-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n @for (entry of host.authEntries(); track entry.id) {\n @let auth = host.asAuth(entry);\n @let lifetime = host.authTokenLifetime(entry);\n <div [class.et-query-devtools-card--armed]=\"lifetime.ttlSeconds !== null\" class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'auth provider' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"auth.isAuthenticated()\"\n [class.et-query-devtools-chip--muted]=\"!auth.isAuthenticated()\"\n class=\"et-query-devtools-chip\"\n >\n {{ auth.isAuthenticated() ? 'authenticated' : 'anonymous' }}\n </span>\n @if (lifetime.ttlSeconds !== null) {\n <span\n [title]=\"\n 'This provider\\'s access token is presented as living ' +\n lifetime.ttlSeconds +\n 's from the moment it was issued, so the refresh happens on that lifetime instead of the real one.'\n \"\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n >\n lifetime {{ lifetime.ttlSeconds }}s\n </span>\n }\n @if (host.authLeadership(auth); as leadership) {\n <span\n [class.et-query-devtools-chip--success]=\"leadership.tone === 'success'\"\n [class.et-query-devtools-chip--muted]=\"leadership.tone === 'muted'\"\n [title]=\"leadership.title\"\n class=\"et-query-devtools-chip\"\n >\n {{ leadership.label }}\n </span>\n }\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.clientBaseUrl || entry.meta.clientName || '\u2014' }}</dd>\n <dt>Access token</dt>\n <dd>{{ auth.accessToken() ? 'present' : 'none' }}</dd>\n <dt>Refresh token</dt>\n <dd>{{ auth.refreshToken() ? 'present' : 'none' }}</dd>\n @if (lifetime.expiresIn; as expiry) {\n <dt>Token expires in</dt>\n <dd>\n {{ expiry }}\n @if (lifetime.realExpiresIn; as real) {\n <small class=\"et-query-devtools-auth-real-expiry\">really {{ real }}</small>\n }\n </dd>\n }\n @if (auth.executionState(); as state) {\n <dt>Execution state</dt>\n <dd>{{ state.type }} \u00B7 {{ state.state }}</dd>\n }\n @if (host.authQueryKeys(auth).length) {\n <dt>Queries</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (key of host.authQueryKeys(auth); track key) {\n <span class=\"et-query-devtools-chip\">{{ key }}</span>\n }\n </div>\n </dd>\n }\n @if (entry.meta.features?.length) {\n <dt>Features</dt>\n <dd>\n <et-query-devtools-features [features]=\"entry.meta.features ?? []\" />\n </dd>\n }\n </dl>\n\n @if (auth.accessToken()) {\n <h4 class=\"et-query-devtools-section\">Token lifetime</h4>\n @if (lifetime.overridable) {\n <div class=\"et-query-devtools-auth-ttl\">\n <div class=\"et-query-devtools-auth-ttl-row\">\n <label class=\"et-query-devtools-field\">\n <span>Override</span>\n <input\n #ttlInput\n [value]=\"lifetime.ttlSeconds ?? ''\"\n [max]=\"TTL_LIMIT\"\n (change)=\"armTokenTtl({ entry: entry, value: ttlInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n step=\"30\"\n />\n <small>seconds, or empty for the lifetime the token was issued with</small>\n </label>\n <div class=\"et-query-devtools-auth-ttl-actions\">\n <button (click)=\"expireTokenNow(entry)\" class=\"et-query-devtools-btn\" type=\"button\">Expire now</button>\n @if (lifetime.ttlSeconds !== null) {\n <button (click)=\"clearTokenTtl(entry)\" class=\"et-query-devtools-btn\" type=\"button\">Clear</button>\n }\n </div>\n </div>\n <p class=\"et-query-devtools-hint\">\n Measured from the token's own <code>iat</code>, so this is a lifetime and not a remaining time - a short\n one on a token issued a while ago refreshes at once, and the token that comes back lives the full\n lifetime. It is what the whole app acts on: the proactive refresh, the expiration warning and the\n countdown above all read it. Never persisted - a reload puts this provider back on its real lifetime.\n </p>\n </div>\n } @else {\n <p class=\"et-query-devtools-hint\">\n This access token carries no <code>exp</code> claim to replace, or no <code>iat</code>/<code>nbf</code> to\n measure a new lifetime from, so it cannot be overridden.\n </p>\n }\n }\n\n @if (host.authTokenPayload(auth); as payload) {\n <h4 class=\"et-query-devtools-section\">Access token payload</h4>\n <et-query-devtools-json [value]=\"payload\" />\n }\n\n @if (auth.latestNonInternalQuery(); as latest) {\n <h4 class=\"et-query-devtools-section\">Latest auth query \u00B7 {{ latest.key }}</h4>\n <et-query-devtools-json [value]=\"latest.snapshot.response()\" />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No auth providers registered.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-auth-real-expiry{color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-auth-ttl{display:flex;flex-direction:column;align-items:start;gap:.8rem}.et-query-devtools-auth-ttl-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:.8rem 1.6rem}.et-query-devtools-auth-ttl-row .et-query-devtools-field{color:var(--_et-qdt-fg)}.et-query-devtools-auth-ttl-row .et-query-devtools-field small{color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-auth-ttl-actions{display:flex;flex-wrap:wrap;gap:.6rem}.et-query-devtools-auth-ttl .et-query-devtools-hint{flex:0 0 auto;max-inline-size:90ch}}\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 }); }
1737
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsAuthTabComponent, isStandalone: true, selector: "et-query-devtools-auth-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n @for (entry of host.authEntries(); track entry.id) {\n @let auth = host.asAuth(entry);\n @let lifetime = host.authTokenLifetime(entry);\n <div [class.et-query-devtools-card--armed]=\"lifetime.ttlSeconds !== null\" class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'auth provider' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"auth.isAuthenticated()\"\n [class.et-query-devtools-chip--muted]=\"!auth.isAuthenticated()\"\n class=\"et-query-devtools-chip\"\n >\n {{ auth.isAuthenticated() ? 'authenticated' : 'anonymous' }}\n </span>\n @if (lifetime.ttlSeconds !== null) {\n <span\n [title]=\"\n 'This provider\\'s access token is presented as living ' +\n lifetime.ttlSeconds +\n 's from the moment it was issued, so the refresh happens on that lifetime instead of the real one.'\n \"\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n >\n lifetime {{ lifetime.ttlSeconds }}s\n </span>\n }\n @if (host.authLeadership(auth); as leadership) {\n <span\n [class.et-query-devtools-chip--success]=\"leadership.tone === 'success'\"\n [class.et-query-devtools-chip--muted]=\"leadership.tone === 'muted'\"\n [title]=\"leadership.title\"\n class=\"et-query-devtools-chip\"\n >\n {{ leadership.label }}\n </span>\n }\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.clientBaseUrl || entry.meta.clientName || '\u2014' }}</dd>\n <dt>Access token</dt>\n <dd>{{ auth.accessToken() ? 'present' : 'none' }}</dd>\n <dt>Refresh token</dt>\n <dd>{{ auth.refreshToken() ? 'present' : 'none' }}</dd>\n @if (lifetime.expiresIn; as expiry) {\n <dt>Token expires in</dt>\n <dd>\n {{ expiry }}\n @if (lifetime.realExpiresIn; as real) {\n <small class=\"et-query-devtools-auth-real-expiry\">really {{ real }}</small>\n }\n </dd>\n }\n @if (auth.executionState(); as state) {\n <dt>Execution state</dt>\n <dd>{{ state.type }} \u00B7 {{ state.state }}</dd>\n }\n @if (host.authQueryKeys(auth).length) {\n <dt>Queries</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (key of host.authQueryKeys(auth); track key) {\n <span class=\"et-query-devtools-chip\">{{ key }}</span>\n }\n </div>\n </dd>\n }\n @if (entry.meta.features?.length) {\n <dt>Features</dt>\n <dd>\n <et-query-devtools-features [features]=\"entry.meta.features ?? []\" />\n </dd>\n }\n </dl>\n\n <h4 class=\"et-query-devtools-section\">Sessions</h4>\n @if (production()) {\n <p class=\"et-query-devtools-hint\">\n A production env is the pick. Nothing of a real user's session is kept here - no tokens, no credentials - so\n the vault and the accounts stand down until a different backend is picked.\n </p>\n } @else {\n @let sessions = sessionsFor(entry);\n @let accounts = accountsFor(entry);\n <div class=\"et-query-devtools-auth-vault\">\n <div class=\"et-query-devtools-auth-vault-actions\">\n <button\n [class.et-query-devtools-btn--active]=\"isTabLocal(entry)\"\n [attr.aria-pressed]=\"isTabLocal(entry)\"\n [title]=\"\n isTabLocal(entry)\n ? 'Hands this tab back to the session every other tab shares. Reloads the page.'\n : 'Keeps this session in this tab alone, so another tab can be somebody else. Reloads the page.'\n \"\n (click)=\"toggleTabLocal(entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ isTabLocal(entry) ? 'Rejoin the shared session' : 'Own this tab' }}\n </button>\n <button\n [disabled]=\"!auth.isAuthenticated()\"\n (click)=\"logout(entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Log out\n </button>\n <button\n [class.et-query-devtools-btn--active]=\"reloadOnSwitch()\"\n [attr.aria-pressed]=\"reloadOnSwitch()\"\n (click)=\"setReloadOnSwitch(!reloadOnSwitch())\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"A switch only replaces what the query layer holds. An app that keeps the user in a service, the router or an open form is still showing the last one until it boots again.\"\n >\n reload on switch\n </button>\n </div>\n\n @if (isTabLocal(entry)) {\n <p class=\"et-query-devtools-hint\">\n This tab holds its own session. It syncs nothing to the other tabs and writes no persistence cookie, so\n every other tab stays on whoever it was.\n </p>\n }\n\n @for (session of sessions; track session.id) {\n <div\n [class.et-query-devtools-auth-session--active]=\"isActive(entry, session)\"\n class=\"et-query-devtools-auth-session\"\n >\n <input\n #labelInput\n [value]=\"session.label\"\n (change)=\"rename(session, labelInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-auth-session-label\"\n type=\"text\"\n aria-label=\"Session name\"\n />\n @if (isActive(entry, session)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--success\">in force</span>\n }\n @if (isExpired(session)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"The stored access token is spent. Switching to it refreshes on the stored refresh token, which works while the server has not spent that one too.\"\n >\n token expired\n </span>\n }\n <span class=\"et-query-devtools-count\"\n >{{ session.subject ?? 'no subject' }} \u00B7 {{ sessionAge(session) }}</span\n >\n <div class=\"et-query-devtools-auth-session-buttons\">\n <button\n [disabled]=\"isActive(entry, session)\"\n (click)=\"switchTo(session)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Switch\n </button>\n <button (click)=\"forget(session)\" class=\"et-query-devtools-btn\" type=\"button\">Forget</button>\n </div>\n </div>\n } @empty {\n <p class=\"et-query-devtools-hint\">\n No session stored for this backend yet. Log in as you normally would and this one appears here, ready to\n be switched back to once you are somebody else.\n </p>\n }\n\n @if (otherScopeCount(entry); as other) {\n <small class=\"et-query-devtools-auth-vault-other\">\n {{ other }} more stored under a different API env. Pick that env again to switch to them.\n </small>\n }\n\n <div class=\"et-query-devtools-auth-vault-head\">\n <strong>Accounts</strong>\n <span class=\"et-query-devtools-count\">credentials are typed in here and stay in this browser</span>\n <button (click)=\"toggleAdding(entry)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ isAdding(entry) ? 'Cancel' : 'Add account' }}\n </button>\n </div>\n\n @if (isAdding(entry)) {\n <div class=\"et-query-devtools-auth-account-add\">\n <input\n #newLabel\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"Admin\"\n aria-label=\"Account name\"\n />\n <select #newQuery class=\"et-query-devtools-select\" aria-label=\"Login query\">\n @for (key of loginQueries(entry); track key) {\n <option [value]=\"key\">{{ key }}</option>\n }\n </select>\n <button\n (click)=\"addAccount({ entry: entry, label: newLabel.value, loginQuery: newQuery.value })\"\n class=\"et-query-devtools-btn et-query-devtools-btn--primary\"\n type=\"button\"\n >\n Add\n </button>\n </div>\n }\n\n @for (account of accounts; track account.id) {\n <div class=\"et-query-devtools-auth-account\">\n <div class=\"et-query-devtools-auth-account-head\">\n <strong>{{ account.label }}</strong>\n @if (account.note) {\n <span class=\"et-query-devtools-count\">{{ account.note }}</span>\n }\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ account.loginQuery }}</span>\n @if (!account.ready) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">needs credentials</span>\n }\n <div class=\"et-query-devtools-auth-session-buttons\">\n <button\n [disabled]=\"!account.ready\"\n (click)=\"login(account)\"\n class=\"et-query-devtools-btn et-query-devtools-btn--primary\"\n type=\"button\"\n >\n Log in as\n </button>\n <button (click)=\"toggleCredentials(account)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ isExpanded(account) ? 'Hide' : 'Credentials' }}\n </button>\n @if (!account.declared) {\n <button (click)=\"removeAccount(account)\" class=\"et-query-devtools-btn\" type=\"button\">Remove</button>\n }\n </div>\n </div>\n @if (isExpanded(account)) {\n <div class=\"et-query-devtools-auth-account-fields\">\n @for (field of account.fields; track field.name) {\n <label class=\"et-query-devtools-field\">\n <span>{{ field.label ?? field.name }}</span>\n <input\n #fieldInput\n [value]=\"account.values[field.name] ?? ''\"\n [type]=\"field.type ?? 'text'\"\n [attr.autocomplete]=\"field.type === 'password' ? 'new-password' : 'off'\"\n (change)=\"setCredential({ account: account, field: field.name, value: fieldInput.value })\"\n class=\"et-query-devtools-input\"\n />\n </label>\n }\n <button (click)=\"clearCredentials(account)\" class=\"et-query-devtools-btn\" type=\"button\">\n Forget these\n </button>\n <p class=\"et-query-devtools-hint\">\n Kept in this browser's <code class=\"et-query-devtools-mono\">localStorage</code> under the API env in\n force, and sent to nothing but this provider's own login query. Never put them in the repository\n instead.\n </p>\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (auth.accessToken()) {\n <h4 class=\"et-query-devtools-section\">Token lifetime</h4>\n @if (lifetime.overridable) {\n <div class=\"et-query-devtools-auth-ttl\">\n <div class=\"et-query-devtools-auth-ttl-row\">\n <label class=\"et-query-devtools-field\">\n <span>Override</span>\n <input\n #ttlInput\n [value]=\"lifetime.ttlSeconds ?? ''\"\n [max]=\"TTL_LIMIT\"\n (change)=\"armTokenTtl({ entry: entry, value: ttlInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n step=\"30\"\n />\n <small>seconds, or empty for the lifetime the token was issued with</small>\n </label>\n <div class=\"et-query-devtools-auth-ttl-actions\">\n <button (click)=\"expireTokenNow(entry)\" class=\"et-query-devtools-btn\" type=\"button\">Expire now</button>\n @if (lifetime.ttlSeconds !== null) {\n <button (click)=\"clearTokenTtl(entry)\" class=\"et-query-devtools-btn\" type=\"button\">Clear</button>\n }\n </div>\n </div>\n <p class=\"et-query-devtools-hint\">\n Measured from the token's own <code>iat</code>, so this is a lifetime and not a remaining time - a short\n one on a token issued a while ago refreshes at once, and the token that comes back lives the full\n lifetime. It is what the whole app acts on: the proactive refresh, the expiration warning and the\n countdown above all read it. Never persisted - a reload puts this provider back on its real lifetime.\n </p>\n </div>\n } @else {\n <p class=\"et-query-devtools-hint\">\n This access token carries no <code>exp</code> claim to replace, or no <code>iat</code>/<code>nbf</code> to\n measure a new lifetime from, so it cannot be overridden.\n </p>\n }\n }\n\n @if (host.authTokenPayload(auth); as payload) {\n <h4 class=\"et-query-devtools-section\">Access token payload</h4>\n <et-query-devtools-json [value]=\"payload\" />\n }\n\n @if (auth.latestNonInternalQuery(); as latest) {\n <h4 class=\"et-query-devtools-section\">Latest auth query \u00B7 {{ latest.key }}</h4>\n <et-query-devtools-json [value]=\"latest.snapshot.response()\" />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No auth providers registered.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-auth-real-expiry{color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-auth-ttl{display:flex;flex-direction:column;align-items:start;gap:.8rem}.et-query-devtools-auth-ttl-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:.8rem 1.6rem}.et-query-devtools-auth-ttl-row .et-query-devtools-field{color:var(--_et-qdt-fg)}.et-query-devtools-auth-ttl-row .et-query-devtools-field small{color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-auth-ttl-actions{display:flex;flex-wrap:wrap;gap:.6rem}.et-query-devtools-auth-ttl .et-query-devtools-hint{flex:0 0 auto;max-inline-size:90ch}.et-query-devtools-auth-vault{display:flex;flex-direction:column;gap:.8rem}.et-query-devtools-auth-vault-actions,.et-query-devtools-auth-account-add{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem}.et-query-devtools-auth-vault-head{display:flex;flex-wrap:wrap;align-items:center;gap:.8rem;margin-block-start:.8rem}.et-query-devtools-auth-vault-other{color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-auth-session,.et-query-devtools-auth-account{padding:.8rem;border-radius:.6rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));box-shadow:inset 0 0 0 1px var(--_et-qdt-border)}.et-query-devtools-auth-session{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem .8rem}.et-query-devtools-auth-session--active{box-shadow:inset 0 0 0 1px var(--_et-qdt-accent)}.et-query-devtools-auth-session-label{inline-size:18rem}.et-query-devtools-auth-session-buttons{display:flex;flex-wrap:wrap;gap:.6rem;margin-inline-start:auto}.et-query-devtools-auth-account{display:flex;flex-direction:column;gap:.8rem}.et-query-devtools-auth-account-head{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem .8rem}.et-query-devtools-auth-account-fields{display:flex;flex-wrap:wrap;align-items:end;gap:.8rem 1.6rem}.et-query-devtools-auth-account-fields .et-query-devtools-field{color:var(--_et-qdt-fg)}.et-query-devtools-auth-account-fields .et-query-devtools-hint{flex:1 1 100%;max-inline-size:90ch}}\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 }); }
1567
1738
  }
1568
1739
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsAuthTabComponent, decorators: [{
1569
1740
  type: Component,
1570
- args: [{ selector: 'et-query-devtools-auth-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsFeaturesComponent, QueryDevtoolsJsonComponent], template: "<div class=\"et-query-devtools-scroll\">\n @for (entry of host.authEntries(); track entry.id) {\n @let auth = host.asAuth(entry);\n @let lifetime = host.authTokenLifetime(entry);\n <div [class.et-query-devtools-card--armed]=\"lifetime.ttlSeconds !== null\" class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'auth provider' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"auth.isAuthenticated()\"\n [class.et-query-devtools-chip--muted]=\"!auth.isAuthenticated()\"\n class=\"et-query-devtools-chip\"\n >\n {{ auth.isAuthenticated() ? 'authenticated' : 'anonymous' }}\n </span>\n @if (lifetime.ttlSeconds !== null) {\n <span\n [title]=\"\n 'This provider\\'s access token is presented as living ' +\n lifetime.ttlSeconds +\n 's from the moment it was issued, so the refresh happens on that lifetime instead of the real one.'\n \"\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n >\n lifetime {{ lifetime.ttlSeconds }}s\n </span>\n }\n @if (host.authLeadership(auth); as leadership) {\n <span\n [class.et-query-devtools-chip--success]=\"leadership.tone === 'success'\"\n [class.et-query-devtools-chip--muted]=\"leadership.tone === 'muted'\"\n [title]=\"leadership.title\"\n class=\"et-query-devtools-chip\"\n >\n {{ leadership.label }}\n </span>\n }\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.clientBaseUrl || entry.meta.clientName || '\u2014' }}</dd>\n <dt>Access token</dt>\n <dd>{{ auth.accessToken() ? 'present' : 'none' }}</dd>\n <dt>Refresh token</dt>\n <dd>{{ auth.refreshToken() ? 'present' : 'none' }}</dd>\n @if (lifetime.expiresIn; as expiry) {\n <dt>Token expires in</dt>\n <dd>\n {{ expiry }}\n @if (lifetime.realExpiresIn; as real) {\n <small class=\"et-query-devtools-auth-real-expiry\">really {{ real }}</small>\n }\n </dd>\n }\n @if (auth.executionState(); as state) {\n <dt>Execution state</dt>\n <dd>{{ state.type }} \u00B7 {{ state.state }}</dd>\n }\n @if (host.authQueryKeys(auth).length) {\n <dt>Queries</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (key of host.authQueryKeys(auth); track key) {\n <span class=\"et-query-devtools-chip\">{{ key }}</span>\n }\n </div>\n </dd>\n }\n @if (entry.meta.features?.length) {\n <dt>Features</dt>\n <dd>\n <et-query-devtools-features [features]=\"entry.meta.features ?? []\" />\n </dd>\n }\n </dl>\n\n @if (auth.accessToken()) {\n <h4 class=\"et-query-devtools-section\">Token lifetime</h4>\n @if (lifetime.overridable) {\n <div class=\"et-query-devtools-auth-ttl\">\n <div class=\"et-query-devtools-auth-ttl-row\">\n <label class=\"et-query-devtools-field\">\n <span>Override</span>\n <input\n #ttlInput\n [value]=\"lifetime.ttlSeconds ?? ''\"\n [max]=\"TTL_LIMIT\"\n (change)=\"armTokenTtl({ entry: entry, value: ttlInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n step=\"30\"\n />\n <small>seconds, or empty for the lifetime the token was issued with</small>\n </label>\n <div class=\"et-query-devtools-auth-ttl-actions\">\n <button (click)=\"expireTokenNow(entry)\" class=\"et-query-devtools-btn\" type=\"button\">Expire now</button>\n @if (lifetime.ttlSeconds !== null) {\n <button (click)=\"clearTokenTtl(entry)\" class=\"et-query-devtools-btn\" type=\"button\">Clear</button>\n }\n </div>\n </div>\n <p class=\"et-query-devtools-hint\">\n Measured from the token's own <code>iat</code>, so this is a lifetime and not a remaining time - a short\n one on a token issued a while ago refreshes at once, and the token that comes back lives the full\n lifetime. It is what the whole app acts on: the proactive refresh, the expiration warning and the\n countdown above all read it. Never persisted - a reload puts this provider back on its real lifetime.\n </p>\n </div>\n } @else {\n <p class=\"et-query-devtools-hint\">\n This access token carries no <code>exp</code> claim to replace, or no <code>iat</code>/<code>nbf</code> to\n measure a new lifetime from, so it cannot be overridden.\n </p>\n }\n }\n\n @if (host.authTokenPayload(auth); as payload) {\n <h4 class=\"et-query-devtools-section\">Access token payload</h4>\n <et-query-devtools-json [value]=\"payload\" />\n }\n\n @if (auth.latestNonInternalQuery(); as latest) {\n <h4 class=\"et-query-devtools-section\">Latest auth query \u00B7 {{ latest.key }}</h4>\n <et-query-devtools-json [value]=\"latest.snapshot.response()\" />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No auth providers registered.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-auth-real-expiry{color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-auth-ttl{display:flex;flex-direction:column;align-items:start;gap:.8rem}.et-query-devtools-auth-ttl-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:.8rem 1.6rem}.et-query-devtools-auth-ttl-row .et-query-devtools-field{color:var(--_et-qdt-fg)}.et-query-devtools-auth-ttl-row .et-query-devtools-field small{color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-auth-ttl-actions{display:flex;flex-wrap:wrap;gap:.6rem}.et-query-devtools-auth-ttl .et-query-devtools-hint{flex:0 0 auto;max-inline-size:90ch}}\n"] }]
1741
+ args: [{ selector: 'et-query-devtools-auth-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsFeaturesComponent, QueryDevtoolsJsonComponent], template: "<div class=\"et-query-devtools-scroll\">\n @for (entry of host.authEntries(); track entry.id) {\n @let auth = host.asAuth(entry);\n @let lifetime = host.authTokenLifetime(entry);\n <div [class.et-query-devtools-card--armed]=\"lifetime.ttlSeconds !== null\" class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'auth provider' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"auth.isAuthenticated()\"\n [class.et-query-devtools-chip--muted]=\"!auth.isAuthenticated()\"\n class=\"et-query-devtools-chip\"\n >\n {{ auth.isAuthenticated() ? 'authenticated' : 'anonymous' }}\n </span>\n @if (lifetime.ttlSeconds !== null) {\n <span\n [title]=\"\n 'This provider\\'s access token is presented as living ' +\n lifetime.ttlSeconds +\n 's from the moment it was issued, so the refresh happens on that lifetime instead of the real one.'\n \"\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n >\n lifetime {{ lifetime.ttlSeconds }}s\n </span>\n }\n @if (host.authLeadership(auth); as leadership) {\n <span\n [class.et-query-devtools-chip--success]=\"leadership.tone === 'success'\"\n [class.et-query-devtools-chip--muted]=\"leadership.tone === 'muted'\"\n [title]=\"leadership.title\"\n class=\"et-query-devtools-chip\"\n >\n {{ leadership.label }}\n </span>\n }\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.clientBaseUrl || entry.meta.clientName || '\u2014' }}</dd>\n <dt>Access token</dt>\n <dd>{{ auth.accessToken() ? 'present' : 'none' }}</dd>\n <dt>Refresh token</dt>\n <dd>{{ auth.refreshToken() ? 'present' : 'none' }}</dd>\n @if (lifetime.expiresIn; as expiry) {\n <dt>Token expires in</dt>\n <dd>\n {{ expiry }}\n @if (lifetime.realExpiresIn; as real) {\n <small class=\"et-query-devtools-auth-real-expiry\">really {{ real }}</small>\n }\n </dd>\n }\n @if (auth.executionState(); as state) {\n <dt>Execution state</dt>\n <dd>{{ state.type }} \u00B7 {{ state.state }}</dd>\n }\n @if (host.authQueryKeys(auth).length) {\n <dt>Queries</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (key of host.authQueryKeys(auth); track key) {\n <span class=\"et-query-devtools-chip\">{{ key }}</span>\n }\n </div>\n </dd>\n }\n @if (entry.meta.features?.length) {\n <dt>Features</dt>\n <dd>\n <et-query-devtools-features [features]=\"entry.meta.features ?? []\" />\n </dd>\n }\n </dl>\n\n <h4 class=\"et-query-devtools-section\">Sessions</h4>\n @if (production()) {\n <p class=\"et-query-devtools-hint\">\n A production env is the pick. Nothing of a real user's session is kept here - no tokens, no credentials - so\n the vault and the accounts stand down until a different backend is picked.\n </p>\n } @else {\n @let sessions = sessionsFor(entry);\n @let accounts = accountsFor(entry);\n <div class=\"et-query-devtools-auth-vault\">\n <div class=\"et-query-devtools-auth-vault-actions\">\n <button\n [class.et-query-devtools-btn--active]=\"isTabLocal(entry)\"\n [attr.aria-pressed]=\"isTabLocal(entry)\"\n [title]=\"\n isTabLocal(entry)\n ? 'Hands this tab back to the session every other tab shares. Reloads the page.'\n : 'Keeps this session in this tab alone, so another tab can be somebody else. Reloads the page.'\n \"\n (click)=\"toggleTabLocal(entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ isTabLocal(entry) ? 'Rejoin the shared session' : 'Own this tab' }}\n </button>\n <button\n [disabled]=\"!auth.isAuthenticated()\"\n (click)=\"logout(entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Log out\n </button>\n <button\n [class.et-query-devtools-btn--active]=\"reloadOnSwitch()\"\n [attr.aria-pressed]=\"reloadOnSwitch()\"\n (click)=\"setReloadOnSwitch(!reloadOnSwitch())\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"A switch only replaces what the query layer holds. An app that keeps the user in a service, the router or an open form is still showing the last one until it boots again.\"\n >\n reload on switch\n </button>\n </div>\n\n @if (isTabLocal(entry)) {\n <p class=\"et-query-devtools-hint\">\n This tab holds its own session. It syncs nothing to the other tabs and writes no persistence cookie, so\n every other tab stays on whoever it was.\n </p>\n }\n\n @for (session of sessions; track session.id) {\n <div\n [class.et-query-devtools-auth-session--active]=\"isActive(entry, session)\"\n class=\"et-query-devtools-auth-session\"\n >\n <input\n #labelInput\n [value]=\"session.label\"\n (change)=\"rename(session, labelInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-auth-session-label\"\n type=\"text\"\n aria-label=\"Session name\"\n />\n @if (isActive(entry, session)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--success\">in force</span>\n }\n @if (isExpired(session)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"The stored access token is spent. Switching to it refreshes on the stored refresh token, which works while the server has not spent that one too.\"\n >\n token expired\n </span>\n }\n <span class=\"et-query-devtools-count\"\n >{{ session.subject ?? 'no subject' }} \u00B7 {{ sessionAge(session) }}</span\n >\n <div class=\"et-query-devtools-auth-session-buttons\">\n <button\n [disabled]=\"isActive(entry, session)\"\n (click)=\"switchTo(session)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Switch\n </button>\n <button (click)=\"forget(session)\" class=\"et-query-devtools-btn\" type=\"button\">Forget</button>\n </div>\n </div>\n } @empty {\n <p class=\"et-query-devtools-hint\">\n No session stored for this backend yet. Log in as you normally would and this one appears here, ready to\n be switched back to once you are somebody else.\n </p>\n }\n\n @if (otherScopeCount(entry); as other) {\n <small class=\"et-query-devtools-auth-vault-other\">\n {{ other }} more stored under a different API env. Pick that env again to switch to them.\n </small>\n }\n\n <div class=\"et-query-devtools-auth-vault-head\">\n <strong>Accounts</strong>\n <span class=\"et-query-devtools-count\">credentials are typed in here and stay in this browser</span>\n <button (click)=\"toggleAdding(entry)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ isAdding(entry) ? 'Cancel' : 'Add account' }}\n </button>\n </div>\n\n @if (isAdding(entry)) {\n <div class=\"et-query-devtools-auth-account-add\">\n <input\n #newLabel\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"Admin\"\n aria-label=\"Account name\"\n />\n <select #newQuery class=\"et-query-devtools-select\" aria-label=\"Login query\">\n @for (key of loginQueries(entry); track key) {\n <option [value]=\"key\">{{ key }}</option>\n }\n </select>\n <button\n (click)=\"addAccount({ entry: entry, label: newLabel.value, loginQuery: newQuery.value })\"\n class=\"et-query-devtools-btn et-query-devtools-btn--primary\"\n type=\"button\"\n >\n Add\n </button>\n </div>\n }\n\n @for (account of accounts; track account.id) {\n <div class=\"et-query-devtools-auth-account\">\n <div class=\"et-query-devtools-auth-account-head\">\n <strong>{{ account.label }}</strong>\n @if (account.note) {\n <span class=\"et-query-devtools-count\">{{ account.note }}</span>\n }\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ account.loginQuery }}</span>\n @if (!account.ready) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">needs credentials</span>\n }\n <div class=\"et-query-devtools-auth-session-buttons\">\n <button\n [disabled]=\"!account.ready\"\n (click)=\"login(account)\"\n class=\"et-query-devtools-btn et-query-devtools-btn--primary\"\n type=\"button\"\n >\n Log in as\n </button>\n <button (click)=\"toggleCredentials(account)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ isExpanded(account) ? 'Hide' : 'Credentials' }}\n </button>\n @if (!account.declared) {\n <button (click)=\"removeAccount(account)\" class=\"et-query-devtools-btn\" type=\"button\">Remove</button>\n }\n </div>\n </div>\n @if (isExpanded(account)) {\n <div class=\"et-query-devtools-auth-account-fields\">\n @for (field of account.fields; track field.name) {\n <label class=\"et-query-devtools-field\">\n <span>{{ field.label ?? field.name }}</span>\n <input\n #fieldInput\n [value]=\"account.values[field.name] ?? ''\"\n [type]=\"field.type ?? 'text'\"\n [attr.autocomplete]=\"field.type === 'password' ? 'new-password' : 'off'\"\n (change)=\"setCredential({ account: account, field: field.name, value: fieldInput.value })\"\n class=\"et-query-devtools-input\"\n />\n </label>\n }\n <button (click)=\"clearCredentials(account)\" class=\"et-query-devtools-btn\" type=\"button\">\n Forget these\n </button>\n <p class=\"et-query-devtools-hint\">\n Kept in this browser's <code class=\"et-query-devtools-mono\">localStorage</code> under the API env in\n force, and sent to nothing but this provider's own login query. Never put them in the repository\n instead.\n </p>\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (auth.accessToken()) {\n <h4 class=\"et-query-devtools-section\">Token lifetime</h4>\n @if (lifetime.overridable) {\n <div class=\"et-query-devtools-auth-ttl\">\n <div class=\"et-query-devtools-auth-ttl-row\">\n <label class=\"et-query-devtools-field\">\n <span>Override</span>\n <input\n #ttlInput\n [value]=\"lifetime.ttlSeconds ?? ''\"\n [max]=\"TTL_LIMIT\"\n (change)=\"armTokenTtl({ entry: entry, value: ttlInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n step=\"30\"\n />\n <small>seconds, or empty for the lifetime the token was issued with</small>\n </label>\n <div class=\"et-query-devtools-auth-ttl-actions\">\n <button (click)=\"expireTokenNow(entry)\" class=\"et-query-devtools-btn\" type=\"button\">Expire now</button>\n @if (lifetime.ttlSeconds !== null) {\n <button (click)=\"clearTokenTtl(entry)\" class=\"et-query-devtools-btn\" type=\"button\">Clear</button>\n }\n </div>\n </div>\n <p class=\"et-query-devtools-hint\">\n Measured from the token's own <code>iat</code>, so this is a lifetime and not a remaining time - a short\n one on a token issued a while ago refreshes at once, and the token that comes back lives the full\n lifetime. It is what the whole app acts on: the proactive refresh, the expiration warning and the\n countdown above all read it. Never persisted - a reload puts this provider back on its real lifetime.\n </p>\n </div>\n } @else {\n <p class=\"et-query-devtools-hint\">\n This access token carries no <code>exp</code> claim to replace, or no <code>iat</code>/<code>nbf</code> to\n measure a new lifetime from, so it cannot be overridden.\n </p>\n }\n }\n\n @if (host.authTokenPayload(auth); as payload) {\n <h4 class=\"et-query-devtools-section\">Access token payload</h4>\n <et-query-devtools-json [value]=\"payload\" />\n }\n\n @if (auth.latestNonInternalQuery(); as latest) {\n <h4 class=\"et-query-devtools-section\">Latest auth query \u00B7 {{ latest.key }}</h4>\n <et-query-devtools-json [value]=\"latest.snapshot.response()\" />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No auth providers registered.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-auth-real-expiry{color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-auth-ttl{display:flex;flex-direction:column;align-items:start;gap:.8rem}.et-query-devtools-auth-ttl-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:.8rem 1.6rem}.et-query-devtools-auth-ttl-row .et-query-devtools-field{color:var(--_et-qdt-fg)}.et-query-devtools-auth-ttl-row .et-query-devtools-field small{color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-auth-ttl-actions{display:flex;flex-wrap:wrap;gap:.6rem}.et-query-devtools-auth-ttl .et-query-devtools-hint{flex:0 0 auto;max-inline-size:90ch}.et-query-devtools-auth-vault{display:flex;flex-direction:column;gap:.8rem}.et-query-devtools-auth-vault-actions,.et-query-devtools-auth-account-add{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem}.et-query-devtools-auth-vault-head{display:flex;flex-wrap:wrap;align-items:center;gap:.8rem;margin-block-start:.8rem}.et-query-devtools-auth-vault-other{color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-auth-session,.et-query-devtools-auth-account{padding:.8rem;border-radius:.6rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));box-shadow:inset 0 0 0 1px var(--_et-qdt-border)}.et-query-devtools-auth-session{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem .8rem}.et-query-devtools-auth-session--active{box-shadow:inset 0 0 0 1px var(--_et-qdt-accent)}.et-query-devtools-auth-session-label{inline-size:18rem}.et-query-devtools-auth-session-buttons{display:flex;flex-wrap:wrap;gap:.6rem;margin-inline-start:auto}.et-query-devtools-auth-account{display:flex;flex-direction:column;gap:.8rem}.et-query-devtools-auth-account-head{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem .8rem}.et-query-devtools-auth-account-fields{display:flex;flex-wrap:wrap;align-items:end;gap:.8rem 1.6rem}.et-query-devtools-auth-account-fields .et-query-devtools-field{color:var(--_et-qdt-fg)}.et-query-devtools-auth-account-fields .et-query-devtools-hint{flex:1 1 100%;max-inline-size:90ch}}\n"] }]
1571
1742
  }] });
1572
1743
 
1573
1744
  /** How the panel spells a teardown cause out - the same wording in the Events tab and the Cache tab. */
@@ -1856,6 +2027,12 @@ class QueryDevtoolsDetailComponent {
1856
2027
  /** Whether any of the three exports behind the Copy menu has just landed on the clipboard. */
1857
2028
  this.copied = computed(() => this.host.copiedReport() || this.host.copiedCurl() || this.host.copiedInsomnia(), /* @ts-ignore */
1858
2029
  ...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
2030
+ /**
2031
+ * Memoized rather than read straight from the host in the template: the record is built per read, and
2032
+ * a fresh object on every change-detection pass would re-seed the value explorer each time.
2033
+ */
2034
+ this.requestHeaders = computed(() => this.host.requestHeaders(this.sel().query), /* @ts-ignore */
2035
+ ...(ngDevMode ? [{ debugName: "requestHeaders" }] : /* istanbul ignore next */ []));
1859
2036
  this.overrideSetSource = computed(() => {
1860
2037
  const sel = this.sel();
1861
2038
  const url = this.host.requestUrl(sel.query);
@@ -1905,7 +2082,7 @@ class QueryDevtoolsDetailComponent {
1905
2082
  };
1906
2083
  }
1907
2084
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1908
- 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 }); }
2085
+ 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 (requestHeaders(); as headers) {\n <h5\n class=\"et-query-devtools-section\"\n title=\"The headers the last run sent - or, for a query that has not run, the ones its args and its client resolve to now. Headers an HTTP interceptor adds are not visible here.\"\n >\n Request headers\n </h5>\n <et-query-devtools-json\n [value]=\"headers\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"requestHeaders\"\n />\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 }); }
1909
2086
  }
1910
2087
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDetailComponent, decorators: [{
1911
2088
  type: Component,
@@ -1920,7 +2097,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
1920
2097
  QueryDevtoolsJsonComponent,
1921
2098
  QueryDevtoolsOverrideSetMenuComponent,
1922
2099
  QueryDevtoolsRouteComponent,
1923
- ], 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"] }]
2100
+ ], 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 (requestHeaders(); as headers) {\n <h5\n class=\"et-query-devtools-section\"\n title=\"The headers the last run sent - or, for a query that has not run, the ones its args and its client resolve to now. Headers an HTTP interceptor adds are not visible here.\"\n >\n Request headers\n </h5>\n <et-query-devtools-json\n [value]=\"headers\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"requestHeaders\"\n />\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"] }]
1924
2101
  }], propDecorators: { sel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sel", required: true }] }] } });
1925
2102
 
1926
2103
  /**
@@ -3239,7 +3416,7 @@ const summarizeLocks = (options) => {
3239
3416
 
3240
3417
  // Generated from package.json by tools/scripts/generate-version.js - do not edit.
3241
3418
  /** The version of `@ethlete/query-devtools` this build was cut from. */
3242
- const QUERY_DEVTOOLS_VERSION = '1.0.0-next.7';
3419
+ const QUERY_DEVTOOLS_VERSION = '1.0.0-next.9';
3243
3420
 
3244
3421
  /* eslint-disable @typescript-eslint/naming-convention -- the `__export_*` keys are Insomnia's file format */
3245
3422
  const WORKSPACE_ID = 'wrk_ethlete_query_devtools';
@@ -4803,6 +4980,7 @@ class QueryDevtoolsComponent {
4803
4980
  /** "Inspect" mode: hover the live UI to find the query that a component created. */
4804
4981
  this.inspectActive = signal(false, /* @ts-ignore */
4805
4982
  ...(ngDevMode ? [{ debugName: "inspectActive" }] : /* istanbul ignore next */ []));
4983
+ /** @internal */
4806
4984
  this.inspectHover = signal(null, /* @ts-ignore */
4807
4985
  ...(ngDevMode ? [{ debugName: "inspectHover" }] : /* istanbul ignore next */ []));
4808
4986
  /** Inspect run backwards: the box drawn over the element the selected query was created in. */
@@ -5552,6 +5730,25 @@ class QueryDevtoolsComponent {
5552
5730
  queryArgs(query) {
5553
5731
  return query.args() ?? query.subtle.request()?.args ?? null;
5554
5732
  }
5733
+ /**
5734
+ * The headers a query's request carries, as a plain `name: value` record, or `null` for a query that
5735
+ * has none the panel can read. The set the last run sent, so it stays readable after the token it
5736
+ * used was replaced; for a query that has not run, what its current args and its client resolve to
5737
+ * now. `null` while a header provider throws - a secure query's needs an access token.
5738
+ *
5739
+ * The args tree cannot answer this on its own: a `withArgs` query reads its args from the feature's
5740
+ * source, which the secure `Authorization` provider is never written back to, and client-level
5741
+ * headers are merged in per execution rather than being part of the args at all.
5742
+ */
5743
+ requestHeaders(query) {
5744
+ const request = query.subtle.request();
5745
+ const args = this.queryArgs(query);
5746
+ const headers = request?.subtle.lastSentHeaders() ?? this.resolvedHeaders(request, args);
5747
+ if (!headers)
5748
+ return null;
5749
+ const entries = headerEntries(headers);
5750
+ return entries.length ? Object.fromEntries(entries.map(({ k, v }) => [k, v])) : null;
5751
+ }
5555
5752
  queryStatus(query) {
5556
5753
  const state = query.executionState();
5557
5754
  if (!state)
@@ -6462,6 +6659,23 @@ class QueryDevtoolsComponent {
6462
6659
  downloadTextFile(file) {
6463
6660
  this.download({ content: file.content, filename: file.name, type: file.type });
6464
6661
  }
6662
+ /**
6663
+ * The headers an execution of this request would resolve to now: the client's with the per-request
6664
+ * ones merged on top, providers called. `undefined` when a provider throws, which for a secure query
6665
+ * means no access token is available yet.
6666
+ */
6667
+ resolvedHeaders(request, args) {
6668
+ try {
6669
+ if (request)
6670
+ return request.subtle.resolveHeaders();
6671
+ return typeof args?.headers === 'function'
6672
+ ? args.headers()
6673
+ : args?.headers;
6674
+ }
6675
+ catch {
6676
+ return undefined;
6677
+ }
6678
+ }
6465
6679
  /**
6466
6680
  * The same route rendered from a set of args rather than from a query, which is what a batch item leaves
6467
6681
  * behind: the batch destroys its query the moment the item settles.
@@ -6785,17 +6999,8 @@ class QueryDevtoolsComponent {
6785
6999
  * (a secure query's needs an access token), in which case the request is exported without them.
6786
7000
  */
6787
7001
  insomniaHeaders(request, args) {
6788
- try {
6789
- const headers = request
6790
- ? request.subtle.resolveHeaders()
6791
- : typeof args?.headers === 'function'
6792
- ? args.headers()
6793
- : args?.headers;
6794
- return (headers?.keys() ?? []).map((name) => ({ name, value: headers?.getAll(name)?.join(', ') ?? '' }));
6795
- }
6796
- catch {
6797
- return [];
6798
- }
7002
+ const headers = this.resolvedHeaders(request, args);
7003
+ return headers ? headerEntries(headers).map(({ k, v }) => ({ name: k, value: v })) : [];
6799
7004
  }
6800
7005
  sessionClients() {
6801
7006
  return this.cacheView().map((client) => ({