@ethlete/query-devtools 1.0.0-next.10 → 1.0.0-next.12

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,14 +1,36 @@
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
- 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, 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, forgetQueryDevtoolsAuthSessionsFor, logoutQueryDevtoolsAuthSession, setQueryDevtoolsAuthTabLocal, loginQueryDevtoolsAuthAccount, setQueryDevtoolsAuthCredentials, clearQueryDevtoolsAuthCredentials, removeQueryDevtoolsAuthAccount, queryDevtoolsAuthFieldsFor, 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, setQueryDevtoolsUiMounted, clearQueryDevtoolsStore, writeQueryDevtoolsStore, registerEthleteVersion, queryDevtoolsOverridePersistence, setQueryDevtoolsOverridePersistence, clearQueryDevtoolsOverrideStore, clearQueryDevtoolsMockStore, createQueryErrorResponse, applyQueryDevtoolsTokenTtl, canOverrideQueryDevtoolsTokenTtl, sumQueryDevtoolsStats } from '@ethlete/query';
5
- import { tap, Subject, switchMap, timer, interval, animationFrames, NEVER, takeUntil, finalize as finalize$1, EMPTY, startWith, merge, map, fromEvent, filter, take } from 'rxjs';
6
3
  import { takeUntilDestroyed, toSignal, toObservable } from '@angular/core/rxjs-interop';
4
+ import { copyToClipboard, injectStyleManager, injectErrorTheme, ProvideColorDirective, clamp, injectRenderer, injectFileDownload, injectViewportSize, injectIsDocumentVisible, injectBreakpointObserver, signalElementDimensions, randomId, reserveOverlayViewportSpace, createObjectUrlHandle, DragHandleDirective, ResizeHandlesComponent } from '@ethlete/core';
5
+ import { queryDevtoolsAbout, hasQueryDevtoolsOverridesAtPath, detectPaginationShape, isDateShapedLeaf, generateQueryDevtoolsStringPreset, generateQueryDevtoolsNumberPreset, collectLeafPaths, armAllQueryDevtoolsMocks, clearQueryDevtoolsArmedMocks, clearQueryDevtoolsFaults, queryDevtoolsResponseHistory, queryDevtoolsMocks, queryDevtoolsAuthSessions, queryDevtoolsArmedMocks, clearQueryDevtoolsAuthSessions, queryDevtoolsApiEnvs, queryDevtoolsApiEnvValues, setQueryDevtoolsApiEnv, queryDevtoolsSettings, queryDevtoolsAllowsLocalAuthSessions, setQueryDevtoolsOverridesScope, setQueryDevtoolsArmedMocksScope, setQueryDevtoolsFaultsScope, setQueryDevtoolsSettings, QUERY_DEVTOOLS_TOKEN_TTL_LIMIT, queryDevtoolsApiEnvIsProduction, queryDevtoolsAuthSessionsFor, queryDevtoolsAuthAccountsFor, queryDevtoolsAuthOtherScopeCount, queryDevtoolsAuthActive, queryDevtoolsAuthTabLocal, switchQueryDevtoolsAuthSession, renameQueryDevtoolsAuthSession, forgetQueryDevtoolsAuthSession, forgetQueryDevtoolsAuthSessionsFor, logoutQueryDevtoolsAuthSession, setQueryDevtoolsAuthTabLocal, loginQueryDevtoolsAuthAccount, setQueryDevtoolsAuthCredentials, clearQueryDevtoolsAuthCredentials, removeQueryDevtoolsAuthAccount, queryDevtoolsAuthFieldsFor, 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, isQueryDevtoolsRepositoryLive, queryDevtoolsFaults, EMPTY_QUERY_DEVTOOLS_FAULT, isQueryDevtoolsFaultArmed, queryDevtoolsFaultsRestored, queryDevtoolsArmedMocksRestored, restoredQueryDevtoolsOverrides, queryDevtoolsRestoredOverridesScope, clearRestoredQueryDevtoolsOverrides, setQueryDevtoolsUiMounted, clearQueryDevtoolsStore, writeQueryDevtoolsStore, registerEthleteVersion, queryDevtoolsOverridePersistence, setQueryDevtoolsOverridePersistence, clearQueryDevtoolsOverrideStore, clearQueryDevtoolsMockStore, createQueryErrorResponse, applyQueryDevtoolsTokenTtl, canOverrideQueryDevtoolsTokenTtl, resolveQueryHeaders, sumQueryDevtoolsStats } from '@ethlete/query';
6
+ import { Subject, switchMap, timer, tap, EMPTY, interval, scan, animationFrames, NEVER, takeUntil, finalize as finalize$1, startWith, merge, map, fromEvent, filter, take } from 'rxjs';
7
7
  import { MenuDirective, MenuTriggerDirective, MenuSurfaceDirective, MenuComponent, MenuItemComponent, MenuSearchDirective, MenuSeparatorComponent, MenuItemShortcutComponent, SelectComponent, SelectSearchDirective, COMPONENTS_VERSION, MenuCheckboxItemComponent, MenuRadioGroupComponent, MenuRadioItemComponent } from '@ethlete/components';
8
8
  import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
9
9
  import { HttpHeaders, HttpErrorResponse } from '@angular/common/http';
10
10
  import { QUERY_DEVTOOLS_VIEW_STATE_KEY, queryDevtoolsShortcutLabel, QueryDevtoolsToggleComponent } from '@ethlete/query-devtools/toggle';
11
11
 
12
+ /** How long a copy button stays ticked after a successful write. */
13
+ const QUERY_DEVTOOLS_COPIED_RESET_MS = 1200;
14
+ /**
15
+ * The clients the event log has rows from, as both its own picker and the Settings mirror of it offer
16
+ * them. Derived from the log rather than from the registry, because a row is keyed by `baseUrl || name`
17
+ * and a picked key the log cannot match narrows the tab to nothing.
18
+ */
19
+ const queryDevtoolsEventClients = (events, picked) => {
20
+ const clients = new Set(events.map((event) => event.client));
21
+ if (picked)
22
+ clients.add(picked);
23
+ return Array.from(clients).sort();
24
+ };
25
+ /** How the panel spells a teardown cause out - the same wording in the Events tab and the Cache tab. */
26
+ const DESTROY_CAUSE_LABELS = {
27
+ unbind: 'last consumer gone',
28
+ expired: 'unused window over',
29
+ 'unused-cap': 'unused entry cap',
30
+ logout: 'logout',
31
+ manual: 'evicted',
32
+ };
33
+
12
34
  const groupsOf = () => {
13
35
  const about = queryDevtoolsAbout();
14
36
  const groups = [
@@ -35,22 +57,116 @@ class QueryDevtoolsAboutComponent {
35
57
  this.groups = groupsOf();
36
58
  this.copied = signal(false, /* @ts-ignore */
37
59
  ...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
60
+ this.copiedReset$ = new Subject();
61
+ // Each copy restarts the tick countdown; switchMap drops the pending reset of the previous one.
62
+ this.copiedReset$
63
+ .pipe(switchMap(() => timer(QUERY_DEVTOOLS_COPIED_RESET_MS)), tap(() => this.copied.set(false)), takeUntilDestroyed())
64
+ .subscribe();
38
65
  }
39
66
  copy() {
40
67
  const text = this.groups
41
68
  .map((group) => [group.title, ...group.rows.map((row) => ` ${row.label}: ${row.value}`)].join('\n'))
42
69
  .join('\n\n');
43
70
  copyToClipboard(text)
44
- .pipe(tap((ok) => this.copied.set(ok)))
71
+ .pipe(tap((ok) => {
72
+ this.copied.set(ok);
73
+ this.copiedReset$.next();
74
+ }))
45
75
  .subscribe();
46
76
  }
47
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsAboutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
48
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsAboutComponent, isStandalone: true, selector: "et-query-devtools-about", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-about-toolbar\">\n <p class=\"et-query-devtools-hint\">\n What is running, and what a bug report should carry. Also in the <strong>\u2913 Session</strong> export and on\n <code class=\"et-query-devtools-mono\">window.ethlete</code>.\n </p>\n <button (click)=\"copy()\" class=\"et-query-devtools-btn\" type=\"button\">\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copied() ? 'Copied' : 'Copy' }}\n </button>\n </div>\n\n @for (group of groups; track group.title) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ group.title }}</strong>\n </div>\n <dl class=\"et-query-devtools-about-list\">\n @for (row of group.rows; track row.label) {\n <div class=\"et-query-devtools-about-row\">\n <dt class=\"et-query-devtools-mono\">{{ row.label }}</dt>\n <dd class=\"et-query-devtools-mono\">{{ row.value }}</dd>\n </div>\n }\n </dl>\n </div>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-about-toolbar{align-items:flex-start}.et-query-devtools-about-list{display:flex;flex-direction:column;gap:.2rem;margin:0;padding:.6rem .8rem}.et-query-devtools-about-row{display:flex;align-items:baseline;gap:.8rem}.et-query-devtools-about-row dt{flex:0 0 auto;color:var(--_et-qdt-muted)}.et-query-devtools-about-row dd{flex:1 1 auto;margin:0;border-block-end:1px dotted color-mix(in srgb,var(--_et-qdt-interaction) 25%,transparent);text-align:end}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
77
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsAboutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
78
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsAboutComponent, isStandalone: true, selector: "et-query-devtools-about", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-about-toolbar\">\n <p class=\"et-query-devtools-hint\">\n What is running, and what a bug report should carry. Also in the <strong>\u2913 Session</strong> export and on\n <code class=\"et-query-devtools-mono\">window.ethlete</code>.\n </p>\n <button (click)=\"copy()\" class=\"et-query-devtools-btn\" type=\"button\">\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copied() ? 'Copied' : 'Copy' }}\n </button>\n </div>\n\n @for (group of groups; track group.title) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ group.title }}</strong>\n </div>\n <dl class=\"et-query-devtools-about-list\">\n @for (row of group.rows; track row.label) {\n <div class=\"et-query-devtools-about-row\">\n <dt class=\"et-query-devtools-mono\">{{ row.label }}</dt>\n <dd class=\"et-query-devtools-mono\">{{ row.value }}</dd>\n </div>\n }\n </dl>\n </div>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-about-toolbar{align-items:flex-start}.et-query-devtools-about-list{display:flex;flex-direction:column;gap:.2rem;margin:0;padding:.6rem .8rem}.et-query-devtools-about-row{display:flex;align-items:baseline;gap:.8rem}.et-query-devtools-about-row dt{flex:0 0 auto;color:var(--_et-qdt-muted)}.et-query-devtools-about-row dd{flex:1 1 auto;margin:0;border-block-end:1px dotted color-mix(in srgb,var(--_et-qdt-interaction) 25%,transparent);text-align:end}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
49
79
  }
50
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsAboutComponent, decorators: [{
80
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsAboutComponent, decorators: [{
51
81
  type: Component,
52
82
  args: [{ selector: 'et-query-devtools-about', encapsulation: ViewEncapsulation.None, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-about-toolbar\">\n <p class=\"et-query-devtools-hint\">\n What is running, and what a bug report should carry. Also in the <strong>\u2913 Session</strong> export and on\n <code class=\"et-query-devtools-mono\">window.ethlete</code>.\n </p>\n <button (click)=\"copy()\" class=\"et-query-devtools-btn\" type=\"button\">\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copied() ? 'Copied' : 'Copy' }}\n </button>\n </div>\n\n @for (group of groups; track group.title) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ group.title }}</strong>\n </div>\n <dl class=\"et-query-devtools-about-list\">\n @for (row of group.rows; track row.label) {\n <div class=\"et-query-devtools-about-row\">\n <dt class=\"et-query-devtools-mono\">{{ row.label }}</dt>\n <dd class=\"et-query-devtools-mono\">{{ row.value }}</dd>\n </div>\n }\n </dl>\n </div>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-about-toolbar{align-items:flex-start}.et-query-devtools-about-list{display:flex;flex-direction:column;gap:.2rem;margin:0;padding:.6rem .8rem}.et-query-devtools-about-row{display:flex;align-items:baseline;gap:.8rem}.et-query-devtools-about-row dt{flex:0 0 auto;color:var(--_et-qdt-muted)}.et-query-devtools-about-row dd{flex:1 1 auto;margin:0;border-block-end:1px dotted color-mix(in srgb,var(--_et-qdt-interaction) 25%,transparent);text-align:end}}\n"] }]
53
- }] });
83
+ }], ctorParameters: () => [] });
84
+
85
+ /**
86
+ * Reads the clipboard, distinguishing "this browser has no `readText`" from "the user or the permission
87
+ * gate said no". Neither is recoverable here, and both leave the caller to fall back to a box that takes
88
+ * a real paste event - a `ClipboardEvent` needs no permission anywhere.
89
+ *
90
+ * Part of the devtools contract. Not part of the general public contract.
91
+ */
92
+ const readQueryDevtoolsClipboard = () => {
93
+ if (!navigator.clipboard?.readText)
94
+ return Promise.resolve({ ok: false, reason: 'unavailable' });
95
+ return navigator.clipboard.readText().then((text) => ({ ok: true, text }), () => ({ ok: false, reason: 'blocked' }));
96
+ };
97
+ /**
98
+ * The text a real paste event carries, or `null` when it holds no plain text - the fallback path for a
99
+ * blocked {@link readQueryDevtoolsClipboard}.
100
+ *
101
+ * Part of the devtools contract. Not part of the general public contract.
102
+ */
103
+ const textFromQueryDevtoolsPaste = (event) => {
104
+ const text = event.clipboardData?.getData('text/plain');
105
+ return text ? text : null;
106
+ };
107
+ /**
108
+ * The pre-`navigator.clipboard` copy: select the text in an off-screen textarea and let the document
109
+ * copy the selection. It is what makes a copy work on a plain `http://` origin, where
110
+ * `navigator.clipboard` is not exposed at all - a LAN address or a staging box, i.e. where a devtools
111
+ * panel is most often opened. The user's own selection is put back afterwards.
112
+ */
113
+ const copyBySelection = (text) => {
114
+ const doc = globalThis.document;
115
+ if (typeof doc?.execCommand !== 'function')
116
+ return false;
117
+ const area = doc.createElement('textarea');
118
+ area.value = text;
119
+ area.setAttribute('readonly', '');
120
+ area.style.position = 'fixed';
121
+ area.style.opacity = '0';
122
+ area.style.top = '0';
123
+ doc.body.appendChild(area);
124
+ const selection = doc.getSelection();
125
+ const previous = selection?.rangeCount ? selection.getRangeAt(0) : null;
126
+ area.select();
127
+ const copied = execCopy(doc);
128
+ area.remove();
129
+ if (previous && selection) {
130
+ selection.removeAllRanges();
131
+ selection.addRange(previous);
132
+ }
133
+ return copied;
134
+ };
135
+ const execCopy = (doc) => {
136
+ try {
137
+ return doc.execCommand('copy');
138
+ }
139
+ catch {
140
+ return false;
141
+ }
142
+ };
143
+ const bySelection = (text, reason) => copyBySelection(text) ? { ok: true } : { ok: false, reason };
144
+ /**
145
+ * Writes to the clipboard, distinguishing "this browser hands over no clipboard" from "the write was
146
+ * blocked" so a caller can say which happened instead of doing nothing. `html` is written alongside the
147
+ * plain text where the browser takes it, since a rich paste keeps its formatting in Slack.
148
+ *
149
+ * Part of the devtools contract. Not part of the general public contract.
150
+ */
151
+ const writeQueryDevtoolsClipboard = (payload) => {
152
+ const { text, html } = payload;
153
+ const clipboard = navigator.clipboard;
154
+ if (!clipboard?.writeText)
155
+ return Promise.resolve(bySelection(text, 'unavailable'));
156
+ const plain = () => clipboard.writeText(text).then(() => ({ ok: true }));
157
+ if (html !== undefined && 'write' in clipboard && typeof ClipboardItem !== 'undefined') {
158
+ const item = new ClipboardItem({
159
+ 'text/html': new Blob([html], { type: 'text/html' }),
160
+ 'text/plain': new Blob([text], { type: 'text/plain' }),
161
+ });
162
+ return clipboard
163
+ .write([item])
164
+ .then(() => ({ ok: true }))
165
+ .catch(plain)
166
+ .catch(() => bySelection(text, 'blocked'));
167
+ }
168
+ return plain().catch(() => bySelection(text, 'blocked'));
169
+ };
54
170
 
55
171
  /**
56
172
  * The copy menu's caret chrome, as a styles-only component the menu mounts itself - a session that
@@ -59,10 +175,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
59
175
  * @internal
60
176
  */
61
177
  class QueryDevtoolsCopyMenuStylesComponent {
62
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsCopyMenuStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
63
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.7", type: QueryDevtoolsCopyMenuStylesComponent, isStandalone: true, selector: "et-query-devtools-copy-menu-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{et-query-devtools-copy-menu,.et-query-devtools-json-copy-menu{display:contents}.et-query-devtools-json-copy-more{appearance:none;display:inline-flex;align-items:center;justify-content:center;align-self:center;flex:0 0 auto;inline-size:1.4rem;block-size:2.2rem;margin-block:-.4rem;margin-inline-start:-.6rem;border:none;border-radius:var(--_et-qdt-radius-sm, .3rem);background:transparent;color:var(--_et-qdt-subtle, currentColor);cursor:pointer;opacity:0;font:inherit;font-size:1rem;line-height:1}@media(hover:hover){.et-query-devtools-json-copy-more:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-json-copy-more:focus-visible,.et-query-devtools-json-copy-more[data-menu-open]{opacity:1}@media(hover:hover){.et-query-devtools-json-row:hover .et-query-devtools-json-copy-more{opacity:1}}@media(hover:none){.et-query-devtools-json-copy-more{opacity:1}}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
178
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsCopyMenuStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
179
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: QueryDevtoolsCopyMenuStylesComponent, isStandalone: true, selector: "et-query-devtools-copy-menu-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{et-query-devtools-copy-menu,.et-query-devtools-json-copy-menu{display:contents}.et-query-devtools-json-copy-more{appearance:none;display:inline-flex;align-items:center;justify-content:center;align-self:center;flex:0 0 auto;inline-size:1.4rem;block-size:2.2rem;margin-block:-.4rem;margin-inline-start:-.6rem;border:none;border-radius:var(--_et-qdt-radius-sm, .3rem);background:transparent;color:var(--_et-qdt-subtle, currentColor);cursor:pointer;opacity:0;font:inherit;font-size:1rem;line-height:1}@media(hover:hover){.et-query-devtools-json-copy-more:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-json-copy-more:focus-visible,.et-query-devtools-json-copy-more[data-menu-open]{opacity:1}@media(hover:hover){.et-query-devtools-json-row:hover .et-query-devtools-json-copy-more{opacity:1}}@media(hover:none){.et-query-devtools-json-copy-more{opacity:1}}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
64
180
  }
65
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsCopyMenuStylesComponent, decorators: [{
181
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsCopyMenuStylesComponent, decorators: [{
66
182
  type: Component,
67
183
  args: [{ selector: 'et-query-devtools-copy-menu-styles', template: '', encapsulation: ViewEncapsulation.None, styles: ["@layer components{et-query-devtools-copy-menu,.et-query-devtools-json-copy-menu{display:contents}.et-query-devtools-json-copy-more{appearance:none;display:inline-flex;align-items:center;justify-content:center;align-self:center;flex:0 0 auto;inline-size:1.4rem;block-size:2.2rem;margin-block:-.4rem;margin-inline-start:-.6rem;border:none;border-radius:var(--_et-qdt-radius-sm, .3rem);background:transparent;color:var(--_et-qdt-subtle, currentColor);cursor:pointer;opacity:0;font:inherit;font-size:1rem;line-height:1}@media(hover:hover){.et-query-devtools-json-copy-more:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-json-copy-more:focus-visible,.et-query-devtools-json-copy-more[data-menu-open]{opacity:1}@media(hover:hover){.et-query-devtools-json-row:hover .et-query-devtools-json-copy-more{opacity:1}}@media(hover:none){.et-query-devtools-json-copy-more{opacity:1}}}\n"] }]
68
184
  }] });
@@ -89,8 +205,8 @@ class QueryDevtoolsCopyMenuComponent {
89
205
  ...(ngDevMode ? [{ debugName: "namedByKey" }] : /* istanbul ignore next */ []));
90
206
  injectStyleManager().mount(QueryDevtoolsCopyMenuStylesComponent);
91
207
  }
92
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsCopyMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
93
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsCopyMenuComponent, isStandalone: true, selector: "et-query-devtools-copy-menu", inputs: { parentKind: { classPropertyName: "parentKind", publicName: "parentKind", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pick: "pick" }, ngImport: i0, template: `
208
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsCopyMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
209
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsCopyMenuComponent, isStandalone: true, selector: "et-query-devtools-copy-menu", inputs: { parentKind: { classPropertyName: "parentKind", publicName: "parentKind", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pick: "pick" }, ngImport: i0, template: `
94
210
  <div class="et-query-devtools-json-copy-menu" etMenu>
95
211
  <button
96
212
  class="et-query-devtools-json-copy-more"
@@ -117,7 +233,7 @@ class QueryDevtoolsCopyMenuComponent {
117
233
  </div>
118
234
  `, isInline: true, dependencies: [{ kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }], encapsulation: i0.ViewEncapsulation.None }); }
119
235
  }
120
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsCopyMenuComponent, decorators: [{
236
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsCopyMenuComponent, decorators: [{
121
237
  type: Component,
122
238
  args: [{
123
239
  selector: 'et-query-devtools-copy-menu',
@@ -162,6 +278,36 @@ const MAX_DEPTH$4 = 12;
162
278
  /** Stands in for a path one side does not have, so a stored `undefined` is not mistaken for a missing key. */
163
279
  const ABSENT = /* @__PURE__ */ Symbol('absent');
164
280
  const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
281
+ /**
282
+ * How to compare a built-in whose own keys are `[]` or private fields - descending into one by key
283
+ * reports every pair of them as identical. `null` for anything the walk should keep descending into.
284
+ *
285
+ * A `Blob` and an `ArrayBuffer` are compared by size and type, not by their bytes: reading a `Blob` is
286
+ * asynchronous, so two payloads of the same size and type read as identical here.
287
+ */
288
+ const exoticSignature = (value) => {
289
+ if (value instanceof Date)
290
+ return `Date:${value.getTime()}`;
291
+ if (typeof File !== 'undefined' && value instanceof File) {
292
+ return `File:${value.name}:${value.size}:${value.type}:${value.lastModified}`;
293
+ }
294
+ if (typeof Blob !== 'undefined' && value instanceof Blob)
295
+ return `Blob:${value.size}:${value.type}`;
296
+ if (value instanceof ArrayBuffer)
297
+ return `ArrayBuffer:${value.byteLength}`;
298
+ if (ArrayBuffer.isView(value))
299
+ return `${value.constructor.name}:${value.byteLength}`;
300
+ if (value instanceof Map)
301
+ return `Map:${JSON.stringify([...value])}`;
302
+ if (value instanceof Set)
303
+ return `Set:${JSON.stringify([...value])}`;
304
+ if (typeof FormData !== 'undefined' && value instanceof FormData)
305
+ return `FormData:${JSON.stringify([...value])}`;
306
+ return null;
307
+ };
308
+ const signatureOf = (value) => (isRecord(value) ? exoticSignature(value) : null);
309
+ /** A record the walk descends into by key, which the built-ins above are not. */
310
+ const isPlainRecord = (value) => isRecord(value) && exoticSignature(value) === null;
165
311
  const valueAt = (record, key) => (key in record ? record[key] : ABSENT);
166
312
  /**
167
313
  * The `id` of every element, when an array holds records keyed by a unique primitive `id` - the shape an
@@ -186,6 +332,10 @@ const recordIds = (items) => {
186
332
  const isSameValue = (before, after) => {
187
333
  if (Object.is(before, after))
188
334
  return true;
335
+ const beforeSignature = signatureOf(before);
336
+ const afterSignature = signatureOf(after);
337
+ if (beforeSignature !== null || afterSignature !== null)
338
+ return beforeSignature === afterSignature;
189
339
  if (!isRecord(before) && !Array.isArray(before))
190
340
  return false;
191
341
  if (!isRecord(after) && !Array.isArray(after))
@@ -224,7 +374,7 @@ const walk = ({ before, after, path, depth }, acc) => {
224
374
  return;
225
375
  }
226
376
  if (depth < MAX_DEPTH$4) {
227
- if (isRecord(before) && isRecord(after)) {
377
+ if (isPlainRecord(before) && isPlainRecord(after)) {
228
378
  for (const key of new Set([...Object.keys(before), ...Object.keys(after)])) {
229
379
  walk({
230
380
  before: valueAt(before, key),
@@ -369,6 +519,28 @@ const headerEntries = (headers) => {
369
519
  return [];
370
520
  }
371
521
  };
522
+ /** Past this a description stops fitting the diff cell and the form-field row it is written for. */
523
+ const MAX_INLINE_LENGTH = 80;
524
+ /**
525
+ * A one-line description of a value {@link exoticOf} knows, for a place with no room to expand it - a
526
+ * diff cell, a form field's value column. `null` for anything the caller should render itself.
527
+ */
528
+ const inlineExoticOf = (value) => {
529
+ const exotic = exoticOf(value);
530
+ if (!exotic)
531
+ return null;
532
+ const inner = exotic.display ?? exotic.entries.map(({ k, v }) => `${k}: ${safeJson(v)}`).join(', ');
533
+ const rendered = `${exotic.typeName}(${inner})`;
534
+ return rendered.length > MAX_INLINE_LENGTH ? `${rendered.slice(0, MAX_INLINE_LENGTH)}…` : rendered;
535
+ };
536
+ const safeJson = (value) => {
537
+ try {
538
+ return JSON.stringify(value) ?? 'undefined';
539
+ }
540
+ catch {
541
+ return '[unserializable]';
542
+ }
543
+ };
372
544
 
373
545
  /**
374
546
  * The value explorer's tree chrome, as a styles-only component the tree mounts itself - a session that
@@ -377,37 +549,14 @@ const headerEntries = (headers) => {
377
549
  * @internal
378
550
  */
379
551
  class QueryDevtoolsJsonStylesComponent {
380
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsJsonStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
381
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.7", type: QueryDevtoolsJsonStylesComponent, isStandalone: true, selector: "et-query-devtools-json-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{.et-query-devtools-json-node,.et-query-devtools-json-children{display:flex;flex-direction:column}.et-query-devtools-json-children{padding-inline-start:1.4rem;border-inline-start:1px solid var(--_et-qdt-border);margin-inline-start:.4rem}.et-query-devtools-json-row{display:flex;align-items:baseline;gap:.4rem;padding:.1rem 0;font-family:inherit;line-height:1.5}.et-query-devtools-json-toggle{appearance:none;display:flex;align-items:baseline;gap:.4rem;min-inline-size:0;border:none;background:transparent;color:var(--_et-qdt-fg);cursor:pointer;text-align:start;font:inherit;padding:.1rem 0}@media(hover:hover){.et-query-devtools-json-node>.et-query-devtools-json-row:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-json-caret{color:var(--_et-qdt-subtle);inline-size:1rem;flex:0 0 auto}.et-query-devtools-json-key{color:var(--_et-qdt-accent)}.et-query-devtools-json-colon{color:var(--_et-qdt-subtle)}.et-query-devtools-json-type{color:var(--_et-qdt-subtle);border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius-sm);padding:0 .25rem;font-size:.85em;white-space:nowrap}.et-query-devtools-json-preview{color:var(--_et-qdt-subtle)}.et-query-devtools-json-chunk{color:var(--_et-qdt-fg);opacity:.75;font-variant-numeric:tabular-nums}.et-query-devtools-json-value{overflow-wrap:anywhere}.et-query-devtools-json-leaf[data-kind=string] .et-query-devtools-json-value{color:var(--_et-qdt-success)}.et-query-devtools-json-leaf[data-kind=number] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=boolean] .et-query-devtools-json-value{color:var(--_et-qdt-loading)}.et-query-devtools-json-leaf[data-kind=null] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=undefined] .et-query-devtools-json-value{color:var(--_et-qdt-subtle)}.et-query-devtools-json-hit{background:color-mix(in srgb,var(--_et-qdt-loading) 40%,transparent);border-radius:var(--_et-qdt-radius-sm)}.et-query-devtools-json-copy{appearance:none;display:inline-flex;align-items:center;justify-content:center;align-self:center;flex:0 0 auto;inline-size:2.2rem;block-size:2.2rem;margin-block:-.4rem;border:none;border-radius:var(--_et-qdt-radius-sm);background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;opacity:0;font:inherit;font-size:1.3rem;line-height:1}@media(hover:hover){.et-query-devtools-json-copy:hover:not(.et-query-devtools-json-copy--copied){background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-json-copy:focus-visible,.et-query-devtools-json-copy--copied{opacity:1}@media(hover:hover){.et-query-devtools-json-row:hover .et-query-devtools-json-copy{opacity:1}}@media(hover:none){.et-query-devtools-json-copy{opacity:1}}.et-query-devtools-json-copy--copied{color:var(--_et-qdt-success)}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
552
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsJsonStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
553
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: QueryDevtoolsJsonStylesComponent, isStandalone: true, selector: "et-query-devtools-json-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{.et-query-devtools-json-node,.et-query-devtools-json-children{display:flex;flex-direction:column}.et-query-devtools-json-children{padding-inline-start:1.4rem;border-inline-start:1px solid var(--_et-qdt-border);margin-inline-start:.4rem}.et-query-devtools-json-row{display:flex;align-items:baseline;gap:.4rem;padding:.1rem 0;font-family:inherit;line-height:1.5}.et-query-devtools-json-toggle{appearance:none;display:flex;align-items:baseline;gap:.4rem;min-inline-size:0;border:none;background:transparent;color:var(--_et-qdt-fg);cursor:pointer;text-align:start;font:inherit;padding:.1rem 0}@media(hover:hover){.et-query-devtools-json-node>.et-query-devtools-json-row:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-json-caret{color:var(--_et-qdt-subtle);inline-size:1rem;flex:0 0 auto}.et-query-devtools-json-key{color:var(--_et-qdt-accent)}.et-query-devtools-json-colon{color:var(--_et-qdt-subtle)}.et-query-devtools-json-type{color:var(--_et-qdt-subtle);border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius-sm);padding:0 .25rem;font-size:.85em;white-space:nowrap}.et-query-devtools-json-preview{color:var(--_et-qdt-subtle)}.et-query-devtools-json-chunk{color:var(--_et-qdt-fg);opacity:.75;font-variant-numeric:tabular-nums}.et-query-devtools-json-value{overflow-wrap:anywhere}.et-query-devtools-json-leaf[data-kind=string] .et-query-devtools-json-value{color:var(--_et-qdt-success)}.et-query-devtools-json-leaf[data-kind=number] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=boolean] .et-query-devtools-json-value{color:var(--_et-qdt-loading)}.et-query-devtools-json-leaf[data-kind=null] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=undefined] .et-query-devtools-json-value{color:var(--_et-qdt-subtle)}.et-query-devtools-json-hit{background:color-mix(in srgb,var(--_et-qdt-loading) 40%,transparent);border-radius:var(--_et-qdt-radius-sm)}.et-query-devtools-json-copy{appearance:none;display:inline-flex;align-items:center;justify-content:center;align-self:center;flex:0 0 auto;inline-size:2.2rem;block-size:2.2rem;margin-block:-.4rem;border:none;border-radius:var(--_et-qdt-radius-sm);background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;opacity:0;font:inherit;font-size:1.3rem;line-height:1}@media(hover:hover){.et-query-devtools-json-copy:hover:not(.et-query-devtools-json-copy--copied){background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-json-copy:focus-visible,.et-query-devtools-json-copy--copied{opacity:1}@media(hover:hover){.et-query-devtools-json-row:hover .et-query-devtools-json-copy{opacity:1}}@media(hover:none){.et-query-devtools-json-copy{opacity:1}}.et-query-devtools-json-copy--copied{color:var(--_et-qdt-success)}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
382
554
  }
383
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsJsonStylesComponent, decorators: [{
555
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsJsonStylesComponent, decorators: [{
384
556
  type: Component,
385
557
  args: [{ selector: 'et-query-devtools-json-styles', template: '', encapsulation: ViewEncapsulation.None, styles: ["@layer components{.et-query-devtools-json-node,.et-query-devtools-json-children{display:flex;flex-direction:column}.et-query-devtools-json-children{padding-inline-start:1.4rem;border-inline-start:1px solid var(--_et-qdt-border);margin-inline-start:.4rem}.et-query-devtools-json-row{display:flex;align-items:baseline;gap:.4rem;padding:.1rem 0;font-family:inherit;line-height:1.5}.et-query-devtools-json-toggle{appearance:none;display:flex;align-items:baseline;gap:.4rem;min-inline-size:0;border:none;background:transparent;color:var(--_et-qdt-fg);cursor:pointer;text-align:start;font:inherit;padding:.1rem 0}@media(hover:hover){.et-query-devtools-json-node>.et-query-devtools-json-row:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-json-caret{color:var(--_et-qdt-subtle);inline-size:1rem;flex:0 0 auto}.et-query-devtools-json-key{color:var(--_et-qdt-accent)}.et-query-devtools-json-colon{color:var(--_et-qdt-subtle)}.et-query-devtools-json-type{color:var(--_et-qdt-subtle);border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius-sm);padding:0 .25rem;font-size:.85em;white-space:nowrap}.et-query-devtools-json-preview{color:var(--_et-qdt-subtle)}.et-query-devtools-json-chunk{color:var(--_et-qdt-fg);opacity:.75;font-variant-numeric:tabular-nums}.et-query-devtools-json-value{overflow-wrap:anywhere}.et-query-devtools-json-leaf[data-kind=string] .et-query-devtools-json-value{color:var(--_et-qdt-success)}.et-query-devtools-json-leaf[data-kind=number] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=boolean] .et-query-devtools-json-value{color:var(--_et-qdt-loading)}.et-query-devtools-json-leaf[data-kind=null] .et-query-devtools-json-value,.et-query-devtools-json-leaf[data-kind=undefined] .et-query-devtools-json-value{color:var(--_et-qdt-subtle)}.et-query-devtools-json-hit{background:color-mix(in srgb,var(--_et-qdt-loading) 40%,transparent);border-radius:var(--_et-qdt-radius-sm)}.et-query-devtools-json-copy{appearance:none;display:inline-flex;align-items:center;justify-content:center;align-self:center;flex:0 0 auto;inline-size:2.2rem;block-size:2.2rem;margin-block:-.4rem;border:none;border-radius:var(--_et-qdt-radius-sm);background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;opacity:0;font:inherit;font-size:1.3rem;line-height:1}@media(hover:hover){.et-query-devtools-json-copy:hover:not(.et-query-devtools-json-copy--copied){background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-json-copy:focus-visible,.et-query-devtools-json-copy--copied{opacity:1}@media(hover:hover){.et-query-devtools-json-row:hover .et-query-devtools-json-copy{opacity:1}}@media(hover:none){.et-query-devtools-json-copy{opacity:1}}.et-query-devtools-json-copy--copied{color:var(--_et-qdt-success)}}\n"] }]
386
558
  }] });
387
559
 
388
- /**
389
- * Reads the clipboard, distinguishing "this browser has no `readText`" from "the user or the permission
390
- * gate said no". Neither is recoverable here, and both leave the caller to fall back to a box that takes
391
- * a real paste event - a `ClipboardEvent` needs no permission anywhere.
392
- *
393
- * Part of the devtools contract. Not part of the general public contract.
394
- */
395
- const readQueryDevtoolsClipboard = () => {
396
- if (!navigator.clipboard?.readText)
397
- return Promise.resolve({ ok: false, reason: 'unavailable' });
398
- return navigator.clipboard.readText().then((text) => ({ ok: true, text }), () => ({ ok: false, reason: 'blocked' }));
399
- };
400
- /**
401
- * The text a real paste event carries, or `null` when it holds no plain text - the fallback path for a
402
- * blocked {@link readQueryDevtoolsClipboard}.
403
- *
404
- * Part of the devtools contract. Not part of the general public contract.
405
- */
406
- const textFromQueryDevtoolsPaste = (event) => {
407
- const text = event.clipboardData?.getData('text/plain');
408
- return text ? text : null;
409
- };
410
-
411
560
  /**
412
561
  * The override menu's trigger chrome, as a styles-only component the menu mounts itself - a session
413
562
  * that never opens the value explorer's override menu does not inject it.
@@ -415,10 +564,10 @@ const textFromQueryDevtoolsPaste = (event) => {
415
564
  * @internal
416
565
  */
417
566
  class QueryDevtoolsOverrideMenuStylesComponent {
418
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsOverrideMenuStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
419
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.7", type: QueryDevtoolsOverrideMenuStylesComponent, isStandalone: true, selector: "et-query-devtools-override-menu-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{et-query-devtools-override-menu{display:contents}.et-query-devtools-override-menu{display:contents}.et-query-devtools-override-trigger{appearance:none;display:inline-flex;align-items:center;justify-content:center;align-self:center;flex:0 0 auto;inline-size:2.2rem;block-size:2.2rem;margin-block:-.4rem;border:none;border-radius:var(--_et-qdt-radius-sm, .3rem);background:transparent;color:var(--_et-qdt-subtle, currentColor);cursor:pointer;opacity:0;font:inherit;font-size:1.3rem;line-height:1}@media(hover:hover){.et-query-devtools-override-trigger:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-override-trigger:focus-visible,.et-query-devtools-override-trigger[data-menu-open]{opacity:1}@media(hover:hover){.et-query-devtools-json-row:hover .et-query-devtools-override-trigger{opacity:1}}@media(hover:none){.et-query-devtools-override-trigger{opacity:1}}.et-query-devtools-override-error,.et-query-devtools-override-note{margin:4px var(--et-menu-item-padding-inline, 10px) 2px;max-inline-size:240px;font-size:12px}.et-query-devtools-override-error{color:var(--_et-qdt-error, rgb(239 68 68))}.et-query-devtools-override-note{color:var(--_et-qdt-subtle, currentColor)}.et-query-devtools-override-set-menu{display:contents}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
567
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsOverrideMenuStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
568
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: QueryDevtoolsOverrideMenuStylesComponent, isStandalone: true, selector: "et-query-devtools-override-menu-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{et-query-devtools-override-menu{display:contents}.et-query-devtools-override-menu{display:contents}.et-query-devtools-override-trigger{appearance:none;display:inline-flex;align-items:center;justify-content:center;align-self:center;flex:0 0 auto;inline-size:2.2rem;block-size:2.2rem;margin-block:-.4rem;border:none;border-radius:var(--_et-qdt-radius-sm, .3rem);background:transparent;color:var(--_et-qdt-subtle, currentColor);cursor:pointer;opacity:0;font:inherit;font-size:1.3rem;line-height:1}@media(hover:hover){.et-query-devtools-override-trigger:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-override-trigger:focus-visible,.et-query-devtools-override-trigger[data-menu-open]{opacity:1}@media(hover:hover){.et-query-devtools-json-row:hover .et-query-devtools-override-trigger{opacity:1}}@media(hover:none){.et-query-devtools-override-trigger{opacity:1}}.et-query-devtools-override-error,.et-query-devtools-override-note{margin:4px var(--et-menu-item-padding-inline, 10px) 2px;max-inline-size:240px;font-size:12px}.et-query-devtools-override-error{color:var(--_et-qdt-error, rgb(239 68 68))}.et-query-devtools-override-note{color:var(--_et-qdt-subtle, currentColor)}.et-query-devtools-override-set-menu{display:contents}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
420
569
  }
421
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsOverrideMenuStylesComponent, decorators: [{
570
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsOverrideMenuStylesComponent, decorators: [{
422
571
  type: Component,
423
572
  args: [{ selector: 'et-query-devtools-override-menu-styles', template: '', encapsulation: ViewEncapsulation.None, styles: ["@layer components{et-query-devtools-override-menu{display:contents}.et-query-devtools-override-menu{display:contents}.et-query-devtools-override-trigger{appearance:none;display:inline-flex;align-items:center;justify-content:center;align-self:center;flex:0 0 auto;inline-size:2.2rem;block-size:2.2rem;margin-block:-.4rem;border:none;border-radius:var(--_et-qdt-radius-sm, .3rem);background:transparent;color:var(--_et-qdt-subtle, currentColor);cursor:pointer;opacity:0;font:inherit;font-size:1.3rem;line-height:1}@media(hover:hover){.et-query-devtools-override-trigger:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-override-trigger:focus-visible,.et-query-devtools-override-trigger[data-menu-open]{opacity:1}@media(hover:hover){.et-query-devtools-json-row:hover .et-query-devtools-override-trigger{opacity:1}}@media(hover:none){.et-query-devtools-override-trigger{opacity:1}}.et-query-devtools-override-error,.et-query-devtools-override-note{margin:4px var(--et-menu-item-padding-inline, 10px) 2px;max-inline-size:240px;font-size:12px}.et-query-devtools-override-error{color:var(--_et-qdt-error, rgb(239 68 68))}.et-query-devtools-override-note{color:var(--_et-qdt-subtle, currentColor)}.et-query-devtools-override-set-menu{display:contents}}\n"] }]
424
573
  }] });
@@ -695,10 +844,10 @@ class QueryDevtoolsOverrideMenuComponent {
695
844
  this.overrides().arm({ type: 'set', path: this.path(), value });
696
845
  this.rootMenu().closeAll();
697
846
  }
698
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsOverrideMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
699
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsOverrideMenuComponent, isStandalone: true, selector: "et-query-devtools-override-menu", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null }, parentKind: { classPropertyName: "parentKind", publicName: "parentKind", isSignal: true, isRequired: false, transformFunction: null }, overrides: { classPropertyName: "overrides", publicName: "overrides", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "rootMenu", first: true, predicate: ["rootMenu"], descendants: true, isSignal: true }, { propertyName: "customInput", first: true, predicate: ["customValueInput"], descendants: true, isSignal: true }, { propertyName: "pasteInput", first: true, predicate: ["pasteBox"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #rootMenu=\"etMenu\" (openChange)=\"resetEditingState($event)\" class=\"et-query-devtools-override-menu\" etMenu>\n <button\n class=\"et-query-devtools-override-trigger\"\n aria-label=\"Override this value\"\n title=\"Override this value\"\n etMenuTrigger\n type=\"button\"\n >\n \u270E\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (customMode()) {\n <input\n #customValueInput\n [error]=\"menuError()\"\n [placeholder]=\"customPlaceholder()\"\n (keydown.enter)=\"commitCustomValue()\"\n etMenuSearch\n type=\"text\"\n />\n } @else if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"menuError()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste here\"\n />\n }\n </ng-container>\n\n @if (customMode()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"commitCustomValue()\" et-menu-item type=\"button\">Apply</button>\n } @else if (pendingPaste(); as pending) {\n <p class=\"et-query-devtools-override-note\">\n This replaces {{ kind() === 'undefined' ? 'a missing value' : 'the ' + kind() }} with {{ pending.kind }}.\n </p>\n <button (activate)=\"confirmKindChange()\" et-menu-item type=\"button\">Replace anyway</button>\n <button (activate)=\"cancelKindChange()\" et-menu-item type=\"button\">Cancel</button>\n } @else if (pasteMode()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"commitPasteBox()\" et-menu-item type=\"button\">Apply</button>\n\n @if (menuNote(); as note) {\n <p class=\"et-query-devtools-override-note\">{{ note }}</p>\n }\n } @else {\n @switch (kind()) {\n @case ('string') {\n @if (isDate()) {\n <button (activate)=\"applyDatePreset('now')\" et-menu-item type=\"button\">Now</button>\n <button (activate)=\"applyDatePreset('plusDay')\" et-menu-item type=\"button\">+1 day</button>\n <button (activate)=\"applyDatePreset('minusDay')\" et-menu-item type=\"button\">-1 day</button>\n <button (activate)=\"applyDatePreset('farFuture')\" et-menu-item type=\"button\">Far future</button>\n <button (activate)=\"applyDatePreset('farPast')\" et-menu-item type=\"button\">Far past</button>\n <button (activate)=\"applyDatePreset('invalid')\" et-menu-item type=\"button\">Invalid date</button>\n } @else {\n <button (activate)=\"applyStringPreset('short')\" et-menu-item type=\"button\">Short text</button>\n <button (activate)=\"applyStringPreset('long')\" et-menu-item type=\"button\">Long text</button>\n <button (activate)=\"applyStringPreset('longWord')\" et-menu-item type=\"button\">Long word</button>\n <button (activate)=\"applyStringPreset('unicode')\" et-menu-item type=\"button\">Unicode / RTL sample</button>\n }\n }\n @case ('number') {\n <button (activate)=\"applyNumberPreset('zero')\" et-menu-item type=\"button\">Zero</button>\n <button (activate)=\"applyNumberPreset('negative')\" et-menu-item type=\"button\">Negative</button>\n <button (activate)=\"applyNumberPreset('huge')\" et-menu-item type=\"button\">Huge</button>\n }\n @case ('boolean') {\n <button (activate)=\"flipBoolean()\" et-menu-item type=\"button\">Flip</button>\n }\n @case ('array') {\n <button (activate)=\"duplicateArray()\" et-menu-item type=\"button\">Duplicate array</button>\n\n @if (hasArrayItems()) {\n <button (activate)=\"emptyArray()\" et-menu-item type=\"button\">Empty this array</button>\n }\n }\n }\n\n @if (supportsNull()) {\n <button (activate)=\"setValue(null)\" et-menu-item type=\"button\">Set to null</button>\n }\n\n @if (isEmptyValue()) {\n <button (activate)=\"setValue('Ab')\" et-menu-item type=\"button\">Set to text</button>\n <button (activate)=\"setValue(0)\" et-menu-item type=\"button\">Set to number</button>\n <button (activate)=\"setValue(true)\" et-menu-item type=\"button\">Set to true</button>\n <button (activate)=\"setValue({})\" et-menu-item type=\"button\">Set to empty object</button>\n <button (activate)=\"setValue([])\" et-menu-item type=\"button\">Set to empty array</button>\n }\n\n @if (supportsCustomValue()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"startCustomEdit()\" et-menu-item type=\"button\">Custom\u2026</button>\n }\n\n @if (isArrayElement()) {\n <button (activate)=\"duplicateThisItem()\" et-menu-item type=\"button\">Duplicate this item</button>\n }\n\n @if (paginationShape()) {\n <button (activate)=\"resizePagination('shrink')\" et-menu-item type=\"button\">Shrink page by 1</button>\n <button (activate)=\"resizePagination('extend')\" et-menu-item type=\"button\">Extend page by 1</button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteValue()\" et-menu-item type=\"button\">Paste value</button>\n\n @if (kind() === 'array') {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteArrayItem()\" et-menu-item type=\"button\">\n Paste as new item\n </button>\n }\n\n @if (isContainer()) {\n <et-menu-separator />\n\n <div etMenu>\n <button et-menu-item etMenuTrigger type=\"button\">Fill every string</button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"fillStrings('short')\" et-menu-item type=\"button\">Short text</button>\n <button (activate)=\"fillStrings('long')\" et-menu-item type=\"button\">Long text</button>\n <button (activate)=\"fillStrings('longWord')\" et-menu-item type=\"button\">Long word</button>\n <button (activate)=\"fillStrings('unicode')\" et-menu-item type=\"button\">Unicode / RTL sample</button>\n </et-menu>\n </ng-template>\n </div>\n\n <div etMenu>\n <button et-menu-item etMenuTrigger type=\"button\">Fill every number</button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"fillNumbers('zero')\" et-menu-item type=\"button\">Zero</button>\n <button (activate)=\"fillNumbers('negative')\" et-menu-item type=\"button\">Negative</button>\n <button (activate)=\"fillNumbers('huge')\" et-menu-item type=\"button\">Huge</button>\n </et-menu>\n </ng-template>\n </div>\n\n <button (activate)=\"fillBooleans()\" et-menu-item type=\"button\">Flip every boolean</button>\n }\n\n @if (isRemovable()) {\n <et-menu-separator />\n\n <button (activate)=\"deleteThis()\" et-menu-item variant=\"destructive\" type=\"button\">\n {{ isInArray() ? 'Delete this item' : 'Delete this key' }}\n </button>\n }\n\n @if (menuError(); as err) {\n <p [etProvideColor]=\"errorColorTheme\" class=\"et-query-devtools-override-error\" role=\"alert\">{{ err }}</p>\n }\n\n @if (hasArmedOverrides()) {\n <et-menu-separator />\n\n <button (activate)=\"reset()\" et-menu-item variant=\"destructive\" type=\"button\">Reset</button>\n }\n }\n </et-menu>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuSearchDirective, selector: "input[etMenuSearch]", inputs: ["query", "loading", "error"], outputs: ["queryChange"], exportAs: ["etMenuSearch"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "component", type: MenuSeparatorComponent, selector: "et-menu-separator" }, { kind: "directive", type: ProvideColorDirective, selector: "[etProvideColor]", inputs: ["etProvideColor"] }], encapsulation: i0.ViewEncapsulation.None }); }
847
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsOverrideMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
848
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsOverrideMenuComponent, isStandalone: true, selector: "et-query-devtools-override-menu", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null }, parentKind: { classPropertyName: "parentKind", publicName: "parentKind", isSignal: true, isRequired: false, transformFunction: null }, overrides: { classPropertyName: "overrides", publicName: "overrides", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "rootMenu", first: true, predicate: ["rootMenu"], descendants: true, isSignal: true }, { propertyName: "customInput", first: true, predicate: ["customValueInput"], descendants: true, isSignal: true }, { propertyName: "pasteInput", first: true, predicate: ["pasteBox"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #rootMenu=\"etMenu\" (openChange)=\"resetEditingState($event)\" class=\"et-query-devtools-override-menu\" etMenu>\n <button\n class=\"et-query-devtools-override-trigger\"\n aria-label=\"Override this value\"\n title=\"Override this value\"\n etMenuTrigger\n type=\"button\"\n >\n \u270E\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (customMode()) {\n <input\n #customValueInput\n [error]=\"menuError()\"\n [placeholder]=\"customPlaceholder()\"\n (keydown.enter)=\"commitCustomValue()\"\n etMenuSearch\n type=\"text\"\n />\n } @else if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"menuError()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste here\"\n />\n }\n </ng-container>\n\n @if (customMode()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"commitCustomValue()\" et-menu-item type=\"button\">Apply</button>\n } @else if (pendingPaste(); as pending) {\n <p class=\"et-query-devtools-override-note\">\n This replaces {{ kind() === 'undefined' ? 'a missing value' : 'the ' + kind() }} with {{ pending.kind }}.\n </p>\n <button (activate)=\"confirmKindChange()\" et-menu-item type=\"button\">Replace anyway</button>\n <button (activate)=\"cancelKindChange()\" et-menu-item type=\"button\">Cancel</button>\n } @else if (pasteMode()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"commitPasteBox()\" et-menu-item type=\"button\">Apply</button>\n\n @if (menuNote(); as note) {\n <p class=\"et-query-devtools-override-note\">{{ note }}</p>\n }\n } @else {\n @switch (kind()) {\n @case ('string') {\n @if (isDate()) {\n <button (activate)=\"applyDatePreset('now')\" et-menu-item type=\"button\">Now</button>\n <button (activate)=\"applyDatePreset('plusDay')\" et-menu-item type=\"button\">+1 day</button>\n <button (activate)=\"applyDatePreset('minusDay')\" et-menu-item type=\"button\">-1 day</button>\n <button (activate)=\"applyDatePreset('farFuture')\" et-menu-item type=\"button\">Far future</button>\n <button (activate)=\"applyDatePreset('farPast')\" et-menu-item type=\"button\">Far past</button>\n <button (activate)=\"applyDatePreset('invalid')\" et-menu-item type=\"button\">Invalid date</button>\n } @else {\n <button (activate)=\"applyStringPreset('short')\" et-menu-item type=\"button\">Short text</button>\n <button (activate)=\"applyStringPreset('long')\" et-menu-item type=\"button\">Long text</button>\n <button (activate)=\"applyStringPreset('longWord')\" et-menu-item type=\"button\">Long word</button>\n <button (activate)=\"applyStringPreset('unicode')\" et-menu-item type=\"button\">Unicode / RTL sample</button>\n }\n }\n @case ('number') {\n <button (activate)=\"applyNumberPreset('zero')\" et-menu-item type=\"button\">Zero</button>\n <button (activate)=\"applyNumberPreset('negative')\" et-menu-item type=\"button\">Negative</button>\n <button (activate)=\"applyNumberPreset('huge')\" et-menu-item type=\"button\">Huge</button>\n }\n @case ('boolean') {\n <button (activate)=\"flipBoolean()\" et-menu-item type=\"button\">Flip</button>\n }\n @case ('array') {\n <button (activate)=\"duplicateArray()\" et-menu-item type=\"button\">Duplicate array</button>\n\n @if (hasArrayItems()) {\n <button (activate)=\"emptyArray()\" et-menu-item type=\"button\">Empty this array</button>\n }\n }\n }\n\n @if (supportsNull()) {\n <button (activate)=\"setValue(null)\" et-menu-item type=\"button\">Set to null</button>\n }\n\n @if (isEmptyValue()) {\n <button (activate)=\"setValue('Ab')\" et-menu-item type=\"button\">Set to text</button>\n <button (activate)=\"setValue(0)\" et-menu-item type=\"button\">Set to number</button>\n <button (activate)=\"setValue(true)\" et-menu-item type=\"button\">Set to true</button>\n <button (activate)=\"setValue({})\" et-menu-item type=\"button\">Set to empty object</button>\n <button (activate)=\"setValue([])\" et-menu-item type=\"button\">Set to empty array</button>\n }\n\n @if (supportsCustomValue()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"startCustomEdit()\" et-menu-item type=\"button\">Custom\u2026</button>\n }\n\n @if (isArrayElement()) {\n <button (activate)=\"duplicateThisItem()\" et-menu-item type=\"button\">Duplicate this item</button>\n }\n\n @if (paginationShape()) {\n <button (activate)=\"resizePagination('shrink')\" et-menu-item type=\"button\">Shrink page by 1</button>\n <button (activate)=\"resizePagination('extend')\" et-menu-item type=\"button\">Extend page by 1</button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteValue()\" et-menu-item type=\"button\">Paste value</button>\n\n @if (kind() === 'array') {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteArrayItem()\" et-menu-item type=\"button\">\n Paste as new item\n </button>\n }\n\n @if (isContainer()) {\n <et-menu-separator />\n\n <div etMenu>\n <button et-menu-item etMenuTrigger type=\"button\">Fill every string</button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"fillStrings('short')\" et-menu-item type=\"button\">Short text</button>\n <button (activate)=\"fillStrings('long')\" et-menu-item type=\"button\">Long text</button>\n <button (activate)=\"fillStrings('longWord')\" et-menu-item type=\"button\">Long word</button>\n <button (activate)=\"fillStrings('unicode')\" et-menu-item type=\"button\">Unicode / RTL sample</button>\n </et-menu>\n </ng-template>\n </div>\n\n <div etMenu>\n <button et-menu-item etMenuTrigger type=\"button\">Fill every number</button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"fillNumbers('zero')\" et-menu-item type=\"button\">Zero</button>\n <button (activate)=\"fillNumbers('negative')\" et-menu-item type=\"button\">Negative</button>\n <button (activate)=\"fillNumbers('huge')\" et-menu-item type=\"button\">Huge</button>\n </et-menu>\n </ng-template>\n </div>\n\n <button (activate)=\"fillBooleans()\" et-menu-item type=\"button\">Flip every boolean</button>\n }\n\n @if (isRemovable()) {\n <et-menu-separator />\n\n <button (activate)=\"deleteThis()\" et-menu-item variant=\"destructive\" type=\"button\">\n {{ isInArray() ? 'Delete this item' : 'Delete this key' }}\n </button>\n }\n\n @if (menuError(); as err) {\n <p [etProvideColor]=\"errorColorTheme\" class=\"et-query-devtools-override-error\" role=\"alert\">{{ err }}</p>\n }\n\n @if (hasArmedOverrides()) {\n <et-menu-separator />\n\n <button (activate)=\"reset()\" et-menu-item variant=\"destructive\" type=\"button\">Reset</button>\n }\n }\n </et-menu>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuSearchDirective, selector: "input[etMenuSearch]", inputs: ["query", "loading", "error"], outputs: ["queryChange"], exportAs: ["etMenuSearch"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "component", type: MenuSeparatorComponent, selector: "et-menu-separator" }, { kind: "directive", type: ProvideColorDirective, selector: "[etProvideColor]", inputs: ["etProvideColor"] }], encapsulation: i0.ViewEncapsulation.None }); }
700
849
  }
701
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsOverrideMenuComponent, decorators: [{
850
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsOverrideMenuComponent, decorators: [{
702
851
  type: Component,
703
852
  args: [{ selector: 'et-query-devtools-override-menu', encapsulation: ViewEncapsulation.None, imports: [
704
853
  MenuDirective,
@@ -721,8 +870,6 @@ const parseLooseJson = (raw) => {
721
870
  }
722
871
  };
723
872
 
724
- /** How long the copy button stays ticked after a successful write. */
725
- const COPIED_RESET_MS$1 = 1200;
726
873
  /**
727
874
  * Containers with more entries than this are not rendered directly - their children are grouped into
728
875
  * collapsed slices of this size, so a 5000-item array costs 50 rows instead of 5000.
@@ -939,7 +1086,7 @@ class QueryDevtoolsJsonComponent {
939
1086
  injectStyleManager().mount(QueryDevtoolsJsonStylesComponent);
940
1087
  // Each copy restarts the tick countdown; switchMap drops the pending reset of the previous one.
941
1088
  this.copiedReset$
942
- .pipe(switchMap(() => timer(COPIED_RESET_MS$1)), tap(() => this.copied.set(null)), takeUntilDestroyed())
1089
+ .pipe(switchMap(() => timer(QUERY_DEVTOOLS_COPIED_RESET_MS)), tap(() => this.copied.set(null)), takeUntilDestroyed())
943
1090
  .subscribe();
944
1091
  }
945
1092
  childPath(key) {
@@ -966,10 +1113,10 @@ class QueryDevtoolsJsonComponent {
966
1113
  const text = this.copyTextFor(payload);
967
1114
  if (text === null)
968
1115
  return;
969
- navigator.clipboard
970
- ?.writeText(text)
971
- .then(() => this.flagCopied(payload))
972
- .catch(() => undefined);
1116
+ writeQueryDevtoolsClipboard({ text }).then((result) => {
1117
+ if (result.ok)
1118
+ this.flagCopied(payload);
1119
+ });
973
1120
  }
974
1121
  /**
975
1122
  * Containers copy their whole subtree as JSON, slices only the entries they cover; leaves copy
@@ -1015,8 +1162,8 @@ class QueryDevtoolsJsonComponent {
1015
1162
  this.copied.set(payload);
1016
1163
  this.copiedReset$.next();
1017
1164
  }
1018
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsJsonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1019
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsJsonComponent, isStandalone: true, selector: "et-query-devtools-json", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, depth: { classPropertyName: "depth", publicName: "depth", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, chunk: { classPropertyName: "chunk", publicName: "chunk", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null }, expandedPaths: { classPropertyName: "expandedPaths", publicName: "expandedPaths", isSignal: true, isRequired: false, transformFunction: null }, collapsedPaths: { classPropertyName: "collapsedPaths", publicName: "collapsedPaths", isSignal: true, isRequired: false, transformFunction: null }, toggleFn: { classPropertyName: "toggleFn", publicName: "toggleFn", isSignal: true, isRequired: false, transformFunction: null }, jsonPath: { classPropertyName: "jsonPath", publicName: "jsonPath", isSignal: true, isRequired: false, transformFunction: null }, parentKind: { classPropertyName: "parentKind", publicName: "parentKind", isSignal: true, isRequired: false, transformFunction: null }, overrides: { classPropertyName: "overrides", publicName: "overrides", isSignal: true, isRequired: false, transformFunction: null }, annotations: { classPropertyName: "annotations", publicName: "annotations", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1165
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsJsonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1166
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsJsonComponent, isStandalone: true, selector: "et-query-devtools-json", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, depth: { classPropertyName: "depth", publicName: "depth", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, chunk: { classPropertyName: "chunk", publicName: "chunk", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null }, expandedPaths: { classPropertyName: "expandedPaths", publicName: "expandedPaths", isSignal: true, isRequired: false, transformFunction: null }, collapsedPaths: { classPropertyName: "collapsedPaths", publicName: "collapsedPaths", isSignal: true, isRequired: false, transformFunction: null }, toggleFn: { classPropertyName: "toggleFn", publicName: "toggleFn", isSignal: true, isRequired: false, transformFunction: null }, jsonPath: { classPropertyName: "jsonPath", publicName: "jsonPath", isSignal: true, isRequired: false, transformFunction: null }, parentKind: { classPropertyName: "parentKind", publicName: "parentKind", isSignal: true, isRequired: false, transformFunction: null }, overrides: { classPropertyName: "overrides", publicName: "overrides", isSignal: true, isRequired: false, transformFunction: null }, annotations: { classPropertyName: "annotations", publicName: "annotations", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1020
1167
  @if (isContainer()) {
1021
1168
  <div class="et-query-devtools-json-node">
1022
1169
  <div class="et-query-devtools-json-row">
@@ -1053,13 +1200,15 @@ class QueryDevtoolsJsonComponent {
1053
1200
  @if (addressable()) {
1054
1201
  <et-query-devtools-copy-menu [parentKind]="parentKind()" (pick)="copy($event)" />
1055
1202
  }
1056
- @if (overrides(); as overrides) {
1057
- <et-query-devtools-override-menu
1058
- [value]="value()"
1059
- [path]="jsonPath()"
1060
- [parentKind]="parentKind()"
1061
- [overrides]="overrides"
1062
- />
1203
+ @if (!chunk()) {
1204
+ @if (overrides(); as overrides) {
1205
+ <et-query-devtools-override-menu
1206
+ [value]="value()"
1207
+ [path]="jsonPath()"
1208
+ [parentKind]="parentKind()"
1209
+ [overrides]="overrides"
1210
+ />
1211
+ }
1063
1212
  }
1064
1213
  </div>
1065
1214
 
@@ -1137,7 +1286,7 @@ class QueryDevtoolsJsonComponent {
1137
1286
  }
1138
1287
  `, isInline: true, dependencies: [{ 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: QueryDevtoolsCopyMenuComponent, selector: "et-query-devtools-copy-menu", inputs: ["parentKind"], outputs: ["pick"] }, { kind: "component", type: QueryDevtoolsOverrideMenuComponent, selector: "et-query-devtools-override-menu", inputs: ["value", "path", "parentKind", "overrides"] }], encapsulation: i0.ViewEncapsulation.None }); }
1139
1288
  }
1140
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsJsonComponent, decorators: [{
1289
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsJsonComponent, decorators: [{
1141
1290
  type: Component,
1142
1291
  args: [{
1143
1292
  selector: 'et-query-devtools-json',
@@ -1178,13 +1327,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
1178
1327
  @if (addressable()) {
1179
1328
  <et-query-devtools-copy-menu [parentKind]="parentKind()" (pick)="copy($event)" />
1180
1329
  }
1181
- @if (overrides(); as overrides) {
1182
- <et-query-devtools-override-menu
1183
- [value]="value()"
1184
- [path]="jsonPath()"
1185
- [parentKind]="parentKind()"
1186
- [overrides]="overrides"
1187
- />
1330
+ @if (!chunk()) {
1331
+ @if (overrides(); as overrides) {
1332
+ <et-query-devtools-override-menu
1333
+ [value]="value()"
1334
+ [path]="jsonPath()"
1335
+ [parentKind]="parentKind()"
1336
+ [overrides]="overrides"
1337
+ />
1338
+ }
1188
1339
  }
1189
1340
  </div>
1190
1341
 
@@ -1295,14 +1446,28 @@ const buildChunks = (entries, window) => {
1295
1446
  }
1296
1447
  return chunks;
1297
1448
  };
1298
- /** Whether a search term appears anywhere in a subtree - used to keep non-matching slices folded. */
1449
+ /**
1450
+ * Whether a search term appears anywhere in a subtree - used to keep non-matching slices folded. The
1451
+ * values a query holds are the app's own, so a self-referential one has to terminate here.
1452
+ *
1453
+ * `seen` is shared across the whole walk rather than being a per-path set: the search stops at the
1454
+ * first match, so anything already in it was walked to the end and did not match.
1455
+ */
1299
1456
  const matchesDeep = (entry, term) => {
1300
- const { k, v } = entry;
1301
- const exotic = exoticOf(v, k);
1302
- if (exotic ? !exotic.entries : !v || typeof v !== 'object') {
1303
- return displayOf(v, k).toLowerCase().includes(term);
1304
- }
1305
- return entriesOf(v, k).some((child) => child.k.toLowerCase().includes(term) || matchesDeep(child, term));
1457
+ const seen = new Set();
1458
+ const walk = ({ k, v }) => {
1459
+ const exotic = exoticOf(v, k);
1460
+ if (exotic ? !exotic.entries : !v || typeof v !== 'object') {
1461
+ return displayOf(v, k).toLowerCase().includes(term);
1462
+ }
1463
+ if (typeof v === 'object' && v !== null) {
1464
+ if (seen.has(v))
1465
+ return false;
1466
+ seen.add(v);
1467
+ }
1468
+ return entriesOf(v, k).some((child) => child.k.toLowerCase().includes(term) || walk(child));
1469
+ };
1470
+ return walk(entry);
1306
1471
  };
1307
1472
 
1308
1473
  const QUERY_DEVTOOLS_HOST = new InjectionToken('QUERY_DEVTOOLS_HOST');
@@ -1346,7 +1511,7 @@ const SCOPE_ROWS = [
1346
1511
  {
1347
1512
  key: 'authSessions',
1348
1513
  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.',
1514
+ hint: 'The token pairs the Auth tab switches between, and the credentials it logs in with. Local is the default in a development build: a vault that forgets the other user on every reload is not one. None keeps nothing at all.',
1350
1515
  warn: {
1351
1516
  scopes: ['local'],
1352
1517
  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.',
@@ -1419,6 +1584,9 @@ class QueryDevtoolsSettingsComponent {
1419
1584
  ...(ngDevMode ? [{ debugName: "armedMockCount" }] : /* istanbul ignore next */ []));
1420
1585
  this.armedFaultCount = computed(() => this.host.faultClients().filter((client) => client.armed).length, /* @ts-ignore */
1421
1586
  ...(ngDevMode ? [{ debugName: "armedFaultCount" }] : /* istanbul ignore next */ []));
1587
+ /** The same keys the Events tab's own picker offers - see {@link queryDevtoolsEventClients}. */
1588
+ this.eventClients = computed(() => queryDevtoolsEventClients(this.host.eventLog(), this.host.eventClient()), /* @ts-ignore */
1589
+ ...(ngDevMode ? [{ debugName: "eventClients" }] : /* istanbul ignore next */ []));
1422
1590
  this.resetConfirming = signal(false, /* @ts-ignore */
1423
1591
  ...(ngDevMode ? [{ debugName: "resetConfirming" }] : /* istanbul ignore next */ []));
1424
1592
  this.FORGET_SESSIONS = clearQueryDevtoolsAuthSessions;
@@ -1472,6 +1640,12 @@ class QueryDevtoolsSettingsComponent {
1472
1640
  warningFor(row, scope) {
1473
1641
  return row.warn?.scopes.includes(scope) ? row.warn.text : null;
1474
1642
  }
1643
+ /** Why one scope is not on offer for a row, for the button's title, or `null` while it is. */
1644
+ unavailableReason(row, scope) {
1645
+ if (row.key !== 'authSessions' || scope !== 'local' || queryDevtoolsAllowsLocalAuthSessions())
1646
+ return null;
1647
+ return 'Unavailable outside a development build: live access and refresh tokens, and the credentials you typed in, must not outlive this tab on a deployed origin. The vault keeps nothing here unless you pick Session, which keeps it for as long as the tab.';
1648
+ }
1475
1649
  setScope(key, scope) {
1476
1650
  // Everything armed goes through its own module: changing the scope moves an existing store and
1477
1651
  // captures whatever is armed right now, so the choice reads as "keep these".
@@ -1510,12 +1684,12 @@ class QueryDevtoolsSettingsComponent {
1510
1684
  showsGoneQueries() {
1511
1685
  return this.host.queryFacets().has('gone');
1512
1686
  }
1513
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
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 }); }
1687
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1688
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", 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 @let unavailable = unavailableReason(row, option.value);\n <button\n [class.et-query-devtools-btn--active]=\"scope === option.value\"\n [attr.aria-pressed]=\"scope === option.value\"\n [disabled]=\"!!unavailable\"\n [title]=\"unavailable\"\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 @let pickedClient = host.eventClient();\n <select\n #eventClientSelect\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Event log client\"\n >\n <option [selected]=\"!pickedClient\" value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\" [selected]=\"name === pickedClient\">{{ 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 }); }
1515
1689
  }
1516
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsSettingsComponent, decorators: [{
1690
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsSettingsComponent, decorators: [{
1517
1691
  type: Component,
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"] }]
1692
+ 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 @let unavailable = unavailableReason(row, option.value);\n <button\n [class.et-query-devtools-btn--active]=\"scope === option.value\"\n [attr.aria-pressed]=\"scope === option.value\"\n [disabled]=\"!!unavailable\"\n [title]=\"unavailable\"\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 @let pickedClient = host.eventClient();\n <select\n #eventClientSelect\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Event log client\"\n >\n <option [selected]=\"!pickedClient\" value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\" [selected]=\"name === pickedClient\">{{ 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"] }]
1519
1693
  }] });
1520
1694
 
1521
1695
  /**
@@ -1525,10 +1699,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
1525
1699
  * @internal
1526
1700
  */
1527
1701
  class QueryDevtoolsTimelineStylesComponent {
1528
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsTimelineStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1529
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.7", type: QueryDevtoolsTimelineStylesComponent, isStandalone: true, selector: "et-query-devtools-timeline-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{.et-query-devtools-timeline{display:grid;grid-template-columns:minmax(10rem,26rem) minmax(14rem,1fr) max-content max-content max-content;gap:.1rem .8rem;padding-inline:.4rem 1rem}.et-query-devtools-timeline-axis,.et-query-devtools-timeline-row{display:grid;grid-column:1 / -1;grid-template-columns:subgrid;align-items:center}.et-query-devtools-timeline-axis{position:sticky;inset-block-start:0;z-index:1;background:var(--_et-qdt-bg);border-block-end:1px solid var(--_et-qdt-border);padding-block-end:.4rem;margin-block-end:.4rem}.et-query-devtools-timeline-ticks{grid-column:2;position:relative;block-size:1.5rem}.et-query-devtools-timeline-tick{position:absolute;inset-block-start:0;transform:translate(-50%);color:var(--_et-qdt-subtle);font-size:1.1rem;white-space:nowrap}.et-query-devtools-timeline-tick:first-child{transform:none}.et-query-devtools-timeline-tick:last-child{transform:translate(-100%)}.et-query-devtools-timeline-row{appearance:none;border:0;background:transparent;color:inherit;font:inherit;text-align:start;padding:.15rem 0;cursor:pointer}@media(hover:hover){.et-query-devtools-timeline-row:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-timeline-row--active{background:var(--_et-qdt-active)}.et-query-devtools-timeline-label{display:flex;align-items:baseline;gap:.4rem;min-inline-size:0;overflow:hidden}.et-query-devtools-timeline-label .et-query-devtools-url{max-inline-size:none;margin-inline-start:0}.et-query-devtools-timeline-track{position:relative;block-size:1.8rem;border-radius:var(--_et-qdt-radius-sm);overflow:hidden;background-color:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);background-image:repeating-linear-gradient(to right,var(--_et-qdt-border) 0 1px,transparent 1px 25%)}.et-query-devtools-timeline-bar{position:absolute;inset-block:.4rem;min-inline-size:.2rem;border-radius:var(--_et-qdt-radius-sm);background:var(--_et-qdt-subtle)}.et-query-devtools-timeline-bar[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-timeline-bar[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-timeline-bar[data-status=loading]{background:var(--_et-qdt-loading)}.et-query-devtools-timeline-bar[data-status=aborted]{min-inline-size:.5rem;background:color-mix(in srgb,var(--_et-qdt-subtle) 20%,transparent);outline:1px solid color-mix(in srgb,var(--_et-qdt-subtle) 70%,transparent);outline-offset:-1px}.et-query-devtools-timeline-duration,.et-query-devtools-timeline-size{text-align:end;color:var(--_et-qdt-muted);font-size:1.1rem;font-variant-numeric:tabular-nums;white-space:nowrap}.et-query-devtools-timeline-duration{color:var(--_et-qdt-fg)}.et-query-devtools-timeline-flags{display:flex;align-items:center;gap:.3rem;white-space:nowrap}.et-query-devtools-timeline-flags .et-query-devtools-chip{padding:0 .4rem;border-color:transparent;border-radius:var(--_et-qdt-radius-sm);background:color-mix(in srgb,currentcolor 12%,transparent);font-size:1rem;line-height:1.5;font-variant-numeric:tabular-nums}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
1702
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsTimelineStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1703
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: QueryDevtoolsTimelineStylesComponent, isStandalone: true, selector: "et-query-devtools-timeline-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{.et-query-devtools-timeline{display:grid;grid-template-columns:minmax(10rem,26rem) minmax(14rem,1fr) max-content max-content max-content;gap:.1rem .8rem;padding-inline:.4rem 1rem}.et-query-devtools-timeline-axis,.et-query-devtools-timeline-row{display:grid;grid-column:1 / -1;grid-template-columns:subgrid;align-items:center}.et-query-devtools-timeline-axis{position:sticky;inset-block-start:0;z-index:1;background:var(--_et-qdt-bg);border-block-end:1px solid var(--_et-qdt-border);padding-block-end:.4rem;margin-block-end:.4rem}.et-query-devtools-timeline-ticks{grid-column:2;position:relative;block-size:1.5rem}.et-query-devtools-timeline-tick{position:absolute;inset-block-start:0;transform:translate(-50%);color:var(--_et-qdt-subtle);font-size:1.1rem;white-space:nowrap}.et-query-devtools-timeline-tick:first-child{transform:none}.et-query-devtools-timeline-tick:last-child{transform:translate(-100%)}.et-query-devtools-timeline-row{appearance:none;border:0;background:transparent;color:inherit;font:inherit;text-align:start;padding:.15rem 0;cursor:pointer}@media(hover:hover){.et-query-devtools-timeline-row:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-timeline-row--active{background:var(--_et-qdt-active)}.et-query-devtools-timeline-label{display:flex;align-items:baseline;gap:.4rem;min-inline-size:0;overflow:hidden}.et-query-devtools-timeline-label .et-query-devtools-url{max-inline-size:none;margin-inline-start:0}.et-query-devtools-timeline-track{position:relative;block-size:1.8rem;border-radius:var(--_et-qdt-radius-sm);overflow:hidden;background-color:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);background-image:repeating-linear-gradient(to right,var(--_et-qdt-border) 0 1px,transparent 1px 25%)}.et-query-devtools-timeline-bar{position:absolute;inset-block:.4rem;min-inline-size:.2rem;border-radius:var(--_et-qdt-radius-sm);background:var(--_et-qdt-subtle)}.et-query-devtools-timeline-bar[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-timeline-bar[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-timeline-bar[data-status=loading]{background:var(--_et-qdt-loading)}.et-query-devtools-timeline-bar[data-status=aborted]{min-inline-size:.5rem;background:color-mix(in srgb,var(--_et-qdt-subtle) 20%,transparent);outline:1px solid color-mix(in srgb,var(--_et-qdt-subtle) 70%,transparent);outline-offset:-1px}.et-query-devtools-timeline-duration,.et-query-devtools-timeline-size{text-align:end;color:var(--_et-qdt-muted);font-size:1.1rem;font-variant-numeric:tabular-nums;white-space:nowrap}.et-query-devtools-timeline-duration{color:var(--_et-qdt-fg)}.et-query-devtools-timeline-flags{display:flex;align-items:center;gap:.3rem;white-space:nowrap}.et-query-devtools-timeline-flags .et-query-devtools-chip{padding:0 .4rem;border-color:transparent;border-radius:var(--_et-qdt-radius-sm);background:color-mix(in srgb,currentcolor 12%,transparent);font-size:1rem;line-height:1.5;font-variant-numeric:tabular-nums}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
1530
1704
  }
1531
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsTimelineStylesComponent, decorators: [{
1705
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsTimelineStylesComponent, decorators: [{
1532
1706
  type: Component,
1533
1707
  args: [{ selector: 'et-query-devtools-timeline-styles', template: '', encapsulation: ViewEncapsulation.None, styles: ["@layer components{.et-query-devtools-timeline{display:grid;grid-template-columns:minmax(10rem,26rem) minmax(14rem,1fr) max-content max-content max-content;gap:.1rem .8rem;padding-inline:.4rem 1rem}.et-query-devtools-timeline-axis,.et-query-devtools-timeline-row{display:grid;grid-column:1 / -1;grid-template-columns:subgrid;align-items:center}.et-query-devtools-timeline-axis{position:sticky;inset-block-start:0;z-index:1;background:var(--_et-qdt-bg);border-block-end:1px solid var(--_et-qdt-border);padding-block-end:.4rem;margin-block-end:.4rem}.et-query-devtools-timeline-ticks{grid-column:2;position:relative;block-size:1.5rem}.et-query-devtools-timeline-tick{position:absolute;inset-block-start:0;transform:translate(-50%);color:var(--_et-qdt-subtle);font-size:1.1rem;white-space:nowrap}.et-query-devtools-timeline-tick:first-child{transform:none}.et-query-devtools-timeline-tick:last-child{transform:translate(-100%)}.et-query-devtools-timeline-row{appearance:none;border:0;background:transparent;color:inherit;font:inherit;text-align:start;padding:.15rem 0;cursor:pointer}@media(hover:hover){.et-query-devtools-timeline-row:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-timeline-row--active{background:var(--_et-qdt-active)}.et-query-devtools-timeline-label{display:flex;align-items:baseline;gap:.4rem;min-inline-size:0;overflow:hidden}.et-query-devtools-timeline-label .et-query-devtools-url{max-inline-size:none;margin-inline-start:0}.et-query-devtools-timeline-track{position:relative;block-size:1.8rem;border-radius:var(--_et-qdt-radius-sm);overflow:hidden;background-color:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);background-image:repeating-linear-gradient(to right,var(--_et-qdt-border) 0 1px,transparent 1px 25%)}.et-query-devtools-timeline-bar{position:absolute;inset-block:.4rem;min-inline-size:.2rem;border-radius:var(--_et-qdt-radius-sm);background:var(--_et-qdt-subtle)}.et-query-devtools-timeline-bar[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-timeline-bar[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-timeline-bar[data-status=loading]{background:var(--_et-qdt-loading)}.et-query-devtools-timeline-bar[data-status=aborted]{min-inline-size:.5rem;background:color-mix(in srgb,var(--_et-qdt-subtle) 20%,transparent);outline:1px solid color-mix(in srgb,var(--_et-qdt-subtle) 70%,transparent);outline-offset:-1px}.et-query-devtools-timeline-duration,.et-query-devtools-timeline-size{text-align:end;color:var(--_et-qdt-muted);font-size:1.1rem;font-variant-numeric:tabular-nums;white-space:nowrap}.et-query-devtools-timeline-duration{color:var(--_et-qdt-fg)}.et-query-devtools-timeline-flags{display:flex;align-items:center;gap:.3rem;white-space:nowrap}.et-query-devtools-timeline-flags .et-query-devtools-chip{padding:0 .4rem;border-color:transparent;border-radius:var(--_et-qdt-radius-sm);background:color-mix(in srgb,currentcolor 12%,transparent);font-size:1rem;line-height:1.5;font-variant-numeric:tabular-nums}}\n"] }]
1534
1708
  }] });
@@ -1545,8 +1719,8 @@ class QueryDevtoolsFeaturesComponent {
1545
1719
  .replace(/_/g, ' ')
1546
1720
  .toLowerCase();
1547
1721
  }
1548
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsFeaturesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1549
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsFeaturesComponent, isStandalone: true, selector: "et-query-devtools-features", inputs: { features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
1722
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsFeaturesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1723
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsFeaturesComponent, isStandalone: true, selector: "et-query-devtools-features", inputs: { features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
1550
1724
  <ul class="et-query-devtools-features">
1551
1725
  @for (feature of features(); track feature.type) {
1552
1726
  <li class="et-query-devtools-feature">
@@ -1566,7 +1740,7 @@ class QueryDevtoolsFeaturesComponent {
1566
1740
  </ul>
1567
1741
  `, isInline: true, 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:var(--_et-qdt-radius);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"], encapsulation: i0.ViewEncapsulation.None }); }
1568
1742
  }
1569
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsFeaturesComponent, decorators: [{
1743
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsFeaturesComponent, decorators: [{
1570
1744
  type: Component,
1571
1745
  args: [{ selector: 'et-query-devtools-features', template: `
1572
1746
  <ul class="et-query-devtools-features">
@@ -1728,8 +1902,9 @@ class QueryDevtoolsAuthTabComponent {
1728
1902
  clearQueryDevtoolsAuthSessions();
1729
1903
  }
1730
1904
  /**
1731
- * Arms an access-token lifetime from the input's raw value, or disarms on an empty one. Clamped here
1732
- * rather than left to the input's `min`/`max`, which a typed-in (or pasted) value ignores.
1905
+ * Arms an access-token lifetime from the input's raw value, or disarms on an empty one. The value is
1906
+ * clamped by {@link setQueryDevtoolsTokenTtl}, not by the input's `min`/`max` - a typed-in (or pasted)
1907
+ * value ignores those.
1733
1908
  */
1734
1909
  armTokenTtl(options) {
1735
1910
  const { entry, value } = options;
@@ -1749,23 +1924,14 @@ class QueryDevtoolsAuthTabComponent {
1749
1924
  providerName(entry) {
1750
1925
  return entry.meta.name ?? '';
1751
1926
  }
1752
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsAuthTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1753
- 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 @if (sessions.length > 1) {\n <button\n (click)=\"forgetAll(entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drops every session stored for this backend. The accounts and what was typed in for them stay.\"\n >\n Forget all {{ sessions.length }}\n </button>\n }\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 ?? session.identity ?? 'nobody named' }} \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 <input\n #newFields\n [value]=\"defaultFieldNames(entry)\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"username, password\"\n aria-label=\"Login fields\"\n title=\"The body keys this login needs, separated by commas. A name that reads as a secret is masked.\"\n />\n <button\n (click)=\"\n addAccount({\n entry: entry,\n label: newLabel.value,\n loginQuery: newQuery.value,\n fields: newFields.value,\n })\n \"\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}:is(.et-query-devtools-auth-ttl,.et-query-devtools-auth-vault)>.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-account-add .et-query-devtools-input{flex:1 1 14rem;inline-size:auto}.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:var(--_et-qdt-radius-lg);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 }); }
1927
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsAuthTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1928
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", 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 @if (sessions.length > 1) {\n <button\n (click)=\"forgetAll(entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drops every session stored for this backend. The accounts and what was typed in for them stay.\"\n >\n Forget all {{ sessions.length }}\n </button>\n }\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 ?? session.identity ?? 'nobody named' }} \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 <input\n #newFields\n [value]=\"defaultFieldNames(entry)\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"username, password\"\n aria-label=\"Login fields\"\n title=\"The body keys this login needs, separated by commas. A name that reads as a secret is masked.\"\n />\n <button\n (click)=\"\n addAccount({\n entry: entry,\n label: newLabel.value,\n loginQuery: newQuery.value,\n fields: newFields.value,\n })\n \"\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}:is(.et-query-devtools-auth-ttl,.et-query-devtools-auth-vault)>.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-account-add .et-query-devtools-input{flex:1 1 14rem;inline-size:auto}.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:var(--_et-qdt-radius-lg);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 }); }
1754
1929
  }
1755
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsAuthTabComponent, decorators: [{
1930
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsAuthTabComponent, decorators: [{
1756
1931
  type: Component,
1757
1932
  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 @if (sessions.length > 1) {\n <button\n (click)=\"forgetAll(entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drops every session stored for this backend. The accounts and what was typed in for them stay.\"\n >\n Forget all {{ sessions.length }}\n </button>\n }\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 ?? session.identity ?? 'nobody named' }} \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 <input\n #newFields\n [value]=\"defaultFieldNames(entry)\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"username, password\"\n aria-label=\"Login fields\"\n title=\"The body keys this login needs, separated by commas. A name that reads as a secret is masked.\"\n />\n <button\n (click)=\"\n addAccount({\n entry: entry,\n label: newLabel.value,\n loginQuery: newQuery.value,\n fields: newFields.value,\n })\n \"\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}:is(.et-query-devtools-auth-ttl,.et-query-devtools-auth-vault)>.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-account-add .et-query-devtools-input{flex:1 1 14rem;inline-size:auto}.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:var(--_et-qdt-radius-lg);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"] }]
1758
1933
  }] });
1759
1934
 
1760
- /** How the panel spells a teardown cause out - the same wording in the Events tab and the Cache tab. */
1761
- const DESTROY_CAUSE_LABELS = {
1762
- unbind: 'last consumer gone',
1763
- expired: 'unused window over',
1764
- 'unused-cap': 'unused entry cap',
1765
- logout: 'logout',
1766
- manual: 'evicted',
1767
- };
1768
-
1769
1935
  /** The Cache tab: every client's cache entries, their freshness, sync status and disk persistence. */
1770
1936
  class QueryDevtoolsCacheTabComponent {
1771
1937
  constructor() {
@@ -1774,39 +1940,71 @@ class QueryDevtoolsCacheTabComponent {
1774
1940
  dropCauseLabel(cause) {
1775
1941
  return DESTROY_CAUSE_LABELS[cause];
1776
1942
  }
1777
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsCacheTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1778
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsCacheTabComponent, isStandalone: true, selector: "et-query-devtools-cache-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n @for (client of host.cacheView(); track client.name) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.baseUrl || client.name }}</strong>\n <span class=\"et-query-devtools-count\">{{ client.rows.length }} entries</span>\n @if (client.rows.length) {\n <span\n class=\"et-query-devtools-count\"\n title=\"The measured size of every cached response. Sizes are read from the decoded bodies, so they ignore transport compression.\"\n >\n {{ host.formatTransferred(client.bytes, client.isEstimatedBytes) }}\n </span>\n }\n @if (client.unused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Entries with no consumer left. They are waiting out their keepUnusedFor window, not leaking.\"\n >\n {{ client.unused }} collectible\n </span>\n }\n @if (client.client?.subtle?.persistence) {\n <span class=\"et-query-devtools-count\">{{ host.persistedCount(client.client!) }} on disk</span>\n <button (click)=\"host.clearPersistedQueries(client.client!)\" class=\"et-query-devtools-btn\" type=\"button\">\n Clear disk\n </button>\n }\n <button\n [disabled]=\"!client.rows.length\"\n (click)=\"host.evictAllCacheEntries(client.repository)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drop every entry of this client, consumers included - the cold-start check without a reload\"\n >\n Evict all\n </button>\n </div>\n @if (host.clientFeatures(client.client); as features) {\n <et-query-devtools-features [features]=\"features\" />\n }\n <div class=\"et-query-devtools-table-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Request</th>\n <th>Consumers</th>\n <th>Size</th>\n <th>Secure</th>\n <th>Freshness</th>\n <th>Sync</th>\n <th>Disk</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (row of client.rows; track row.entry.key) {\n @let cacheEntry = row.entry;\n @let expanded = host.isCacheValueExpanded(client.name, cacheEntry.key);\n <tr>\n <td [title]=\"cacheEntry.request.method + ' ' + cacheEntry.request.url\">\n <span class=\"et-query-devtools-method\">{{ cacheEntry.request.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(cacheEntry.request.url) }}</span>\n </td>\n <td>\n {{ cacheEntry.consumerCount }}\n <!-- An entry nothing holds any more is the one row worth explaining: it looks\n like a leak and is in fact a keepUnusedFor window running out. -->\n @if (cacheEntry.isUnused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No consumer left - kept for its keepUnusedFor window, then collected\"\n >\n collectible\n </span>\n }\n </td>\n <td>{{ row.bytes ? host.formatTransferred(row.bytes, row.isEstimatedBytes) : '\u2014' }}</td>\n <td>{{ cacheEntry.isSecure ? 'yes' : 'no' }}</td>\n <td>{{ host.cacheFreshness(cacheEntry) }}</td>\n <td>{{ host.cacheSync(cacheEntry, client.pollStates) }}</td>\n <td>{{ host.cachePersistence(cacheEntry) }}</td>\n <td>\n <span class=\"et-query-devtools-cell-actions\">\n <button\n [class.et-query-devtools-btn--active]=\"expanded\"\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.toggleCacheValue(client.name, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Show the response held under this key\"\n >\n Value\n </button>\n <button (click)=\"host.refetchCacheEntry(cacheEntry)\" class=\"et-query-devtools-btn\" type=\"button\">\n Refetch\n </button>\n <button\n (click)=\"host.evictCacheEntry(client.repository, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Evict\n </button>\n </span>\n </td>\n </tr>\n @if (expanded) {\n <tr>\n <td class=\"et-query-devtools-cell-value\" colspan=\"8\">\n <et-query-devtools-json [value]=\"cacheEntry.request.response()\" />\n </td>\n </tr>\n }\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"8\">Cache empty.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (client.dropped.length) {\n <h4 class=\"et-query-devtools-section\">Dropped</h4>\n <ul class=\"et-query-devtools-dropped\">\n @for (entry of client.dropped; track entry.at) {\n <li>\n <span class=\"et-query-devtools-method\">{{ entry.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(entry.url) }}</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{\n dropCauseLabel(entry.cause)\n }}</span>\n <span class=\"et-query-devtools-count\">{{ host.formatTime(entry.at) }}</span>\n </li>\n }\n </ul>\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No clients with a cache yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-dropped{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none;color:var(--_et-qdt-muted)}.et-query-devtools-dropped li{display:flex;align-items:center;gap:.8rem}.et-query-devtools-cell-value{padding-inline-start:1.6rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsFeaturesComponent, selector: "et-query-devtools-features", inputs: ["features"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }], encapsulation: i0.ViewEncapsulation.None }); }
1943
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsCacheTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1944
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsCacheTabComponent, isStandalone: true, selector: "et-query-devtools-cache-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n @for (client of host.cacheView(); track client.name) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.baseUrl || client.name }}</strong>\n <span class=\"et-query-devtools-count\">{{ client.rows.length }} entries</span>\n @if (client.rows.length) {\n <span\n class=\"et-query-devtools-count\"\n title=\"The measured size of every cached response. Sizes are read from the decoded bodies, so they ignore transport compression.\"\n >\n {{ host.formatTransferred(client.bytes, client.isEstimatedBytes) }}\n </span>\n }\n @if (client.unused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Entries with no consumer left. They are waiting out their keepUnusedFor window, not leaking.\"\n >\n {{ client.unused }} collectible\n </span>\n }\n @if (client.client?.subtle?.persistence) {\n <span class=\"et-query-devtools-count\">{{ host.persistedCount(client.client!) }} on disk</span>\n <button (click)=\"host.clearPersistedQueries(client.client!)\" class=\"et-query-devtools-btn\" type=\"button\">\n Clear disk\n </button>\n }\n <button\n [disabled]=\"!client.rows.length\"\n (click)=\"host.evictAllCacheEntries(client.repository)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drop every entry of this client, consumers included - the cold-start check without a reload\"\n >\n Evict all\n </button>\n </div>\n @if (host.clientFeatures(client.client); as features) {\n <et-query-devtools-features [features]=\"features\" />\n }\n <div class=\"et-query-devtools-table-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Request</th>\n <th>Consumers</th>\n <th>Size</th>\n <th>Secure</th>\n <th>Freshness</th>\n <th>Sync</th>\n <th>Disk</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (row of client.rows; track row.entry.key) {\n @let cacheEntry = row.entry;\n @let expanded = host.isCacheValueExpanded(client.name, cacheEntry.key);\n <tr>\n <td [title]=\"cacheEntry.request.method + ' ' + cacheEntry.request.url\">\n <span class=\"et-query-devtools-method\">{{ cacheEntry.request.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(cacheEntry.request.url) }}</span>\n </td>\n <td>\n {{ cacheEntry.consumerCount }}\n <!-- An entry nothing holds any more is the one row worth explaining: it looks\n like a leak and is in fact a keepUnusedFor window running out. -->\n @if (cacheEntry.isUnused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No consumer left - kept for its keepUnusedFor window, then collected\"\n >\n collectible\n </span>\n }\n </td>\n <td>{{ row.bytes ? host.formatTransferred(row.bytes, row.isEstimatedBytes) : '\u2014' }}</td>\n <td>{{ cacheEntry.isSecure ? 'yes' : 'no' }}</td>\n <td>{{ host.cacheFreshness(cacheEntry) }}</td>\n <td>{{ host.cacheSync(cacheEntry, client.pollStates) }}</td>\n <td>{{ host.cachePersistence(cacheEntry) }}</td>\n <td>\n <span class=\"et-query-devtools-cell-actions\">\n <button\n [class.et-query-devtools-btn--active]=\"expanded\"\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.toggleCacheValue(client.name, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Show the response held under this key\"\n >\n Value\n </button>\n <button (click)=\"host.refetchCacheEntry(cacheEntry)\" class=\"et-query-devtools-btn\" type=\"button\">\n Refetch\n </button>\n <button\n (click)=\"host.evictCacheEntry(client.repository, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Evict\n </button>\n </span>\n </td>\n </tr>\n @if (expanded) {\n <tr>\n <td class=\"et-query-devtools-cell-value\" colspan=\"8\">\n <et-query-devtools-json [value]=\"cacheEntry.request.response()\" />\n </td>\n </tr>\n }\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"8\">Cache empty.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (client.dropped.length) {\n <h4 class=\"et-query-devtools-section\">Dropped</h4>\n <ul class=\"et-query-devtools-dropped\">\n @for (entry of client.dropped; track entry.at) {\n <li>\n <span class=\"et-query-devtools-method\">{{ entry.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(entry.url) }}</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{\n dropCauseLabel(entry.cause)\n }}</span>\n <span class=\"et-query-devtools-count\">{{ host.formatTime(entry.at) }}</span>\n </li>\n }\n </ul>\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No clients with a cache yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-dropped{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none;color:var(--_et-qdt-muted)}.et-query-devtools-dropped li{display:flex;align-items:center;gap:.8rem}.et-query-devtools-cell-value{padding-inline-start:1.6rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsFeaturesComponent, selector: "et-query-devtools-features", inputs: ["features"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }], encapsulation: i0.ViewEncapsulation.None }); }
1779
1945
  }
1780
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsCacheTabComponent, decorators: [{
1946
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsCacheTabComponent, decorators: [{
1781
1947
  type: Component,
1782
1948
  args: [{ selector: 'et-query-devtools-cache-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsFeaturesComponent, QueryDevtoolsJsonComponent], template: "<div class=\"et-query-devtools-scroll\">\n @for (client of host.cacheView(); track client.name) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.baseUrl || client.name }}</strong>\n <span class=\"et-query-devtools-count\">{{ client.rows.length }} entries</span>\n @if (client.rows.length) {\n <span\n class=\"et-query-devtools-count\"\n title=\"The measured size of every cached response. Sizes are read from the decoded bodies, so they ignore transport compression.\"\n >\n {{ host.formatTransferred(client.bytes, client.isEstimatedBytes) }}\n </span>\n }\n @if (client.unused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Entries with no consumer left. They are waiting out their keepUnusedFor window, not leaking.\"\n >\n {{ client.unused }} collectible\n </span>\n }\n @if (client.client?.subtle?.persistence) {\n <span class=\"et-query-devtools-count\">{{ host.persistedCount(client.client!) }} on disk</span>\n <button (click)=\"host.clearPersistedQueries(client.client!)\" class=\"et-query-devtools-btn\" type=\"button\">\n Clear disk\n </button>\n }\n <button\n [disabled]=\"!client.rows.length\"\n (click)=\"host.evictAllCacheEntries(client.repository)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drop every entry of this client, consumers included - the cold-start check without a reload\"\n >\n Evict all\n </button>\n </div>\n @if (host.clientFeatures(client.client); as features) {\n <et-query-devtools-features [features]=\"features\" />\n }\n <div class=\"et-query-devtools-table-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Request</th>\n <th>Consumers</th>\n <th>Size</th>\n <th>Secure</th>\n <th>Freshness</th>\n <th>Sync</th>\n <th>Disk</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (row of client.rows; track row.entry.key) {\n @let cacheEntry = row.entry;\n @let expanded = host.isCacheValueExpanded(client.name, cacheEntry.key);\n <tr>\n <td [title]=\"cacheEntry.request.method + ' ' + cacheEntry.request.url\">\n <span class=\"et-query-devtools-method\">{{ cacheEntry.request.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(cacheEntry.request.url) }}</span>\n </td>\n <td>\n {{ cacheEntry.consumerCount }}\n <!-- An entry nothing holds any more is the one row worth explaining: it looks\n like a leak and is in fact a keepUnusedFor window running out. -->\n @if (cacheEntry.isUnused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No consumer left - kept for its keepUnusedFor window, then collected\"\n >\n collectible\n </span>\n }\n </td>\n <td>{{ row.bytes ? host.formatTransferred(row.bytes, row.isEstimatedBytes) : '\u2014' }}</td>\n <td>{{ cacheEntry.isSecure ? 'yes' : 'no' }}</td>\n <td>{{ host.cacheFreshness(cacheEntry) }}</td>\n <td>{{ host.cacheSync(cacheEntry, client.pollStates) }}</td>\n <td>{{ host.cachePersistence(cacheEntry) }}</td>\n <td>\n <span class=\"et-query-devtools-cell-actions\">\n <button\n [class.et-query-devtools-btn--active]=\"expanded\"\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.toggleCacheValue(client.name, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Show the response held under this key\"\n >\n Value\n </button>\n <button (click)=\"host.refetchCacheEntry(cacheEntry)\" class=\"et-query-devtools-btn\" type=\"button\">\n Refetch\n </button>\n <button\n (click)=\"host.evictCacheEntry(client.repository, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Evict\n </button>\n </span>\n </td>\n </tr>\n @if (expanded) {\n <tr>\n <td class=\"et-query-devtools-cell-value\" colspan=\"8\">\n <et-query-devtools-json [value]=\"cacheEntry.request.response()\" />\n </td>\n </tr>\n }\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"8\">Cache empty.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (client.dropped.length) {\n <h4 class=\"et-query-devtools-section\">Dropped</h4>\n <ul class=\"et-query-devtools-dropped\">\n @for (entry of client.dropped; track entry.at) {\n <li>\n <span class=\"et-query-devtools-method\">{{ entry.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(entry.url) }}</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{\n dropCauseLabel(entry.cause)\n }}</span>\n <span class=\"et-query-devtools-count\">{{ host.formatTime(entry.at) }}</span>\n </li>\n }\n </ul>\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No clients with a cache yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-dropped{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none;color:var(--_et-qdt-muted)}.et-query-devtools-dropped li{display:flex;align-items:center;gap:.8rem}.et-query-devtools-cell-value{padding-inline-start:1.6rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}}\n"] }]
1783
1949
  }] });
1784
1950
 
1785
- const jsonBody$1 = (body) => {
1951
+ const binaryLabelOf = (body) => {
1952
+ if (typeof FormData !== 'undefined' && body instanceof FormData)
1953
+ return 'FormData';
1954
+ if (typeof Blob !== 'undefined' && body instanceof Blob)
1955
+ return 'Blob';
1956
+ if (body instanceof ArrayBuffer || ArrayBuffer.isView(body))
1957
+ return 'binary';
1958
+ if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams)
1959
+ return 'URLSearchParams';
1960
+ return null;
1961
+ };
1962
+ const asJson = (body, indent) => {
1786
1963
  try {
1787
- return JSON.stringify(body);
1964
+ const data = JSON.stringify(body, null, indent);
1965
+ return { data, json: true, binary: null, unserializable: false };
1788
1966
  }
1789
1967
  catch {
1790
- // A body holding a circular reference or a throwing `toJSON` cannot be written out.
1791
- return null;
1968
+ return { data: null, json: false, binary: null, unserializable: true };
1792
1969
  }
1793
1970
  };
1971
+ /**
1972
+ * How a request's body reaches an export. `indent` is passed to `JSON.stringify`, so a command can
1973
+ * stay on one line while a collection stays readable.
1974
+ *
1975
+ * Angular sends a string body as `text/plain` and lets the browser label a `FormData`, a `Blob` or an
1976
+ * `ArrayBuffer` itself, so only the shapes `HttpClient` serializes as JSON are reported as `json` -
1977
+ * an export that claims `application/json` for the rest replays a different request than the app sent.
1978
+ */
1979
+ const queryDevtoolsRequestBody = (request, indent = 0) => {
1980
+ if (request.gqlQuery) {
1981
+ const variables = request.body?.variables ?? {};
1982
+ return asJson({ query: request.gqlQuery, variables }, indent);
1983
+ }
1984
+ const body = request.body;
1985
+ if (body === null || body === undefined)
1986
+ return { data: null, json: false, binary: null, unserializable: false };
1987
+ if (typeof body === 'string')
1988
+ return { data: body, json: false, binary: null, unserializable: false };
1989
+ const binary = binaryLabelOf(body);
1990
+ if (binary)
1991
+ return { data: null, json: false, binary, unserializable: false };
1992
+ return asJson(body, indent);
1993
+ };
1994
+
1794
1995
  /**
1795
1996
  * Wraps a value in single quotes for a POSIX shell, which is the one quoting that leaves everything
1796
1997
  * inside untouched - `$`, backticks and backslashes included. A single quote cannot be escaped inside
1797
1998
  * such a string, so each one ends the quoting, emits an escaped quote and opens it again.
1798
1999
  */
1799
2000
  const shellQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
1800
- const bodyOf$1 = (request) => {
1801
- if (request.gqlQuery) {
1802
- const variables = request.body?.variables ?? {};
1803
- return jsonBody$1({ query: request.gqlQuery, variables });
1804
- }
1805
- if (request.body === null || request.body === undefined)
1806
- return null;
1807
- if (typeof request.body === 'string')
1808
- return request.body;
1809
- return jsonBody$1(request.body);
2001
+ /** Why a command sends no body, as a shell comment above it - or `null` when it sends the real one. */
2002
+ const bodyNote = (body) => {
2003
+ if (body.binary)
2004
+ return `# The panel cannot replay a ${body.binary} body - this command sends none.\n`;
2005
+ if (body.unserializable)
2006
+ return '# The panel could not serialize this body - this command sends none.\n';
2007
+ return null;
1810
2008
  };
1811
2009
  /**
1812
2010
  * Renders a request as a copy-pasteable `curl` command, one option per line so a long one stays
@@ -1818,11 +2016,11 @@ const bodyOf$1 = (request) => {
1818
2016
  * without an access token) are left out, so what is emitted is only what was known.
1819
2017
  */
1820
2018
  const buildCurlCommand = (request) => {
1821
- const body = bodyOf$1(request);
2019
+ const body = queryDevtoolsRequestBody(request);
1822
2020
  const headers = request.headers.filter((header) => header.value !== '');
1823
2021
  const hasContentType = headers.some((header) => header.name.toLowerCase() === 'content-type');
1824
- const withContentType = body === null || hasContentType ? headers : [...headers, { name: 'Content-Type', value: 'application/json' }];
1825
- const lines = [`curl ${shellQuote(request.url)}`];
2022
+ const withContentType = !body.json || hasContentType ? headers : [...headers, { name: 'Content-Type', value: 'application/json' }];
2023
+ const lines = [`${bodyNote(body) ?? ''}curl ${shellQuote(request.url)}`];
1826
2024
  // GET is curl's default, so spelling it out only adds noise to the common case.
1827
2025
  if (request.method.toUpperCase() !== 'GET')
1828
2026
  lines.push(`-X ${request.method.toUpperCase()}`);
@@ -1830,8 +2028,8 @@ const buildCurlCommand = (request) => {
1830
2028
  lines.push(`-H ${shellQuote(`${header.name}: ${header.value}`)}`);
1831
2029
  // `--data-raw`, not `-d`: the latter strips newlines and would mangle a GraphQL document, and it also
1832
2030
  // rewrites the method to POST behind your back.
1833
- if (body !== null)
1834
- lines.push(`--data-raw ${shellQuote(body)}`);
2031
+ if (body.data !== null)
2032
+ lines.push(`--data-raw ${shellQuote(body.data)}`);
1835
2033
  return lines.join(' \\\n ');
1836
2034
  };
1837
2035
 
@@ -1870,7 +2068,15 @@ class QueryDevtoolsOverrideSetMenuComponent {
1870
2068
  }
1871
2069
  copySet() {
1872
2070
  const text = serializeQueryDevtoolsOverrideTransfer(this.overrides().list(), this.source());
1873
- navigator.clipboard?.writeText(text).then(() => this.copied.set(true), () => this.error.set('The clipboard write was blocked'));
2071
+ writeQueryDevtoolsClipboard({ text }).then((result) => {
2072
+ if (result.ok) {
2073
+ this.copied.set(true);
2074
+ return;
2075
+ }
2076
+ this.error.set(result.reason === 'unavailable'
2077
+ ? 'This browser will not hand over the clipboard'
2078
+ : 'The clipboard write was blocked');
2079
+ });
1874
2080
  }
1875
2081
  pasteSet() {
1876
2082
  this.reset();
@@ -1925,10 +2131,10 @@ class QueryDevtoolsOverrideSetMenuComponent {
1925
2131
  this.error.set(null);
1926
2132
  this.note.set(`${parts.join('; ')}.`);
1927
2133
  }
1928
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsOverrideSetMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1929
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsOverrideSetMenuComponent, isStandalone: true, selector: "et-query-devtools-override-set-menu", inputs: { overrides: { classPropertyName: "overrides", publicName: "overrides", isSignal: true, isRequired: true, transformFunction: null }, response: { classPropertyName: "response", publicName: "response", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "pasteInput", first: true, predicate: ["pasteBox"], descendants: true, isSignal: true }], ngImport: i0, template: "<div (openChange)=\"resetOnClose($event)\" class=\"et-query-devtools-override-set-menu\" etMenu>\n <button\n et-menu-item\n title=\"Copy every response override armed on this query, or paste a set copied off another one.\"\n type=\"button\"\n etMenuTrigger\n >\n Override set\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"error()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste a copied set here\"\n />\n }\n </ng-container>\n\n @if (armedCount()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"copySet()\" et-menu-item type=\"button\">\n {{ copied() ? '\u2713 Copied' : 'Copy ' + armedCount() + (armedCount() === 1 ? ' armed op' : ' armed ops') }}\n </button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteSet()\" et-menu-item type=\"button\">\n Paste a set{{ armedCount() ? ' on top' : '' }}\n </button>\n\n @if (error(); as err) {\n <p [etProvideColor]=\"errorColorTheme\" class=\"et-query-devtools-override-error\" role=\"alert\">{{ err }}</p>\n } @else if (note(); as text) {\n <p class=\"et-query-devtools-override-note\" role=\"status\">{{ text }}</p>\n }\n </et-menu>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuSearchDirective, selector: "input[etMenuSearch]", inputs: ["query", "loading", "error"], outputs: ["queryChange"], exportAs: ["etMenuSearch"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "directive", type: ProvideColorDirective, selector: "[etProvideColor]", inputs: ["etProvideColor"] }], encapsulation: i0.ViewEncapsulation.None }); }
2134
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsOverrideSetMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2135
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsOverrideSetMenuComponent, isStandalone: true, selector: "et-query-devtools-override-set-menu", inputs: { overrides: { classPropertyName: "overrides", publicName: "overrides", isSignal: true, isRequired: true, transformFunction: null }, response: { classPropertyName: "response", publicName: "response", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "pasteInput", first: true, predicate: ["pasteBox"], descendants: true, isSignal: true }], ngImport: i0, template: "<div (openChange)=\"resetOnClose($event)\" class=\"et-query-devtools-override-set-menu\" etMenu>\n <button\n et-menu-item\n title=\"Copy every response override armed on this query, or paste a set copied off another one.\"\n type=\"button\"\n etMenuTrigger\n >\n Override set\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"error()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste a copied set here\"\n />\n }\n </ng-container>\n\n @if (armedCount()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"copySet()\" et-menu-item type=\"button\">\n {{ copied() ? '\u2713 Copied' : 'Copy ' + armedCount() + (armedCount() === 1 ? ' armed op' : ' armed ops') }}\n </button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteSet()\" et-menu-item type=\"button\">\n Paste a set{{ armedCount() ? ' on top' : '' }}\n </button>\n\n @if (error(); as err) {\n <p [etProvideColor]=\"errorColorTheme\" class=\"et-query-devtools-override-error\" role=\"alert\">{{ err }}</p>\n } @else if (note(); as text) {\n <p class=\"et-query-devtools-override-note\" role=\"status\">{{ text }}</p>\n }\n </et-menu>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuSearchDirective, selector: "input[etMenuSearch]", inputs: ["query", "loading", "error"], outputs: ["queryChange"], exportAs: ["etMenuSearch"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "directive", type: ProvideColorDirective, selector: "[etProvideColor]", inputs: ["etProvideColor"] }], encapsulation: i0.ViewEncapsulation.None }); }
1930
2136
  }
1931
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsOverrideSetMenuComponent, decorators: [{
2137
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsOverrideSetMenuComponent, decorators: [{
1932
2138
  type: Component,
1933
2139
  args: [{ selector: 'et-query-devtools-override-set-menu', encapsulation: ViewEncapsulation.None, imports: [
1934
2140
  MenuDirective,
@@ -1994,8 +2200,8 @@ class QueryDevtoolsRouteComponent {
1994
2200
  .join('')}`, /* @ts-ignore */
1995
2201
  ...(ngDevMode ? [{ debugName: "trimTitle" }] : /* istanbul ignore next */ []));
1996
2202
  }
1997
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1998
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsRouteComponent, isStandalone: true, selector: "et-query-devtools-route", inputs: { segments: { classPropertyName: "segments", publicName: "segments", isSignal: true, isRequired: true, transformFunction: null }, fallback: { classPropertyName: "fallback", publicName: "fallback", isSignal: true, isRequired: false, transformFunction: null }, trimPrefix: { classPropertyName: "trimPrefix", publicName: "trimPrefix", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2203
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2204
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsRouteComponent, isStandalone: true, selector: "et-query-devtools-route", inputs: { segments: { classPropertyName: "segments", publicName: "segments", isSignal: true, isRequired: true, transformFunction: null }, fallback: { classPropertyName: "fallback", publicName: "fallback", isSignal: true, isRequired: false, transformFunction: null }, trimPrefix: { classPropertyName: "trimPrefix", publicName: "trimPrefix", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1999
2205
  @if (trimmed(); as trimmed) {
2000
2206
  <span [attr.title]="trimTitle()" class="et-query-devtools-route-trimmed">…</span>
2001
2207
  @for (segment of trimmed; track $index) {
@@ -2010,7 +2216,7 @@ class QueryDevtoolsRouteComponent {
2010
2216
  }
2011
2217
  `, isInline: true, styles: ["@layer components{.et-query-devtools-route-param{color:var(--_et-qdt-accent)}.et-query-devtools-route-query{color:var(--_et-qdt-muted)}.et-query-devtools-route-trimmed{color:var(--_et-qdt-subtle);cursor:help}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
2012
2218
  }
2013
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsRouteComponent, decorators: [{
2219
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsRouteComponent, decorators: [{
2014
2220
  type: Component,
2015
2221
  args: [{ selector: 'et-query-devtools-route', template: `
2016
2222
  @if (trimmed(); as trimmed) {
@@ -2097,10 +2303,10 @@ class QueryDevtoolsDetailComponent {
2097
2303
  compare: { label: 'Compare', title: 'The newer end of the comparison - click to clear it' },
2098
2304
  };
2099
2305
  }
2100
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2101
- 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:var(--_et-qdt-radius-sm);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:var(--_et-qdt-radius);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:var(--_et-qdt-radius);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 }); }
2306
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2307
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", 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, body and resolved headers - token included</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:var(--_et-qdt-radius-sm);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:var(--_et-qdt-radius);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:var(--_et-qdt-radius);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 }); }
2102
2308
  }
2103
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDetailComponent, decorators: [{
2309
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsDetailComponent, decorators: [{
2104
2310
  type: Component,
2105
2311
  args: [{ selector: 'et-query-devtools-detail', encapsulation: ViewEncapsulation.None, imports: [
2106
2312
  MenuComponent,
@@ -2113,7 +2319,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
2113
2319
  QueryDevtoolsJsonComponent,
2114
2320
  QueryDevtoolsOverrideSetMenuComponent,
2115
2321
  QueryDevtoolsRouteComponent,
2116
- ], 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:var(--_et-qdt-radius-sm);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:var(--_et-qdt-radius);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:var(--_et-qdt-radius);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"] }]
2322
+ ], 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, body and resolved headers - token included</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:var(--_et-qdt-radius-sm);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:var(--_et-qdt-radius);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:var(--_et-qdt-radius);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"] }]
2117
2323
  }], propDecorators: { sel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sel", required: true }] }] } });
2118
2324
 
2119
2325
  /**
@@ -2131,10 +2337,10 @@ class QueryDevtoolsDrawerComponent {
2131
2337
  ...(ngDevMode ? [{ debugName: "container" }] : /* istanbul ignore next */ []));
2132
2338
  this.dismiss = output();
2133
2339
  }
2134
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2135
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsDrawerComponent, isStandalone: true, selector: "et-query-devtools-drawer", inputs: { sel: { classPropertyName: "sel", publicName: "sel", isSignal: true, isRequired: false, transformFunction: null }, container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { dismiss: "dismiss" }, ngImport: i0, template: "@if (sel(); as selected) {\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'drawer', container: container() })\"\n (dblclick)=\"host.resetPaneSize('drawer')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"dismiss.emit()\"\n class=\"et-query-devtools-btn et-query-devtools-icon-btn\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <et-query-devtools-detail [sel]=\"selected\" />\n </aside>\n}\n", styles: ["@layer components{et-query-devtools-drawer{display:contents}.et-query-devtools-drawer{flex:0 0 var(--_et-qdt-drawer-width, 42%);min-inline-size:var(--_et-qdt-drawer-width, 26rem);max-inline-size:var(--_et-qdt-drawer-width, 48rem);overflow-y:auto;padding:0 1rem 1rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-drawer-head{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding:.8rem 0;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg);color:var(--_et-qdt-muted);border-block-end:1px solid var(--_et-qdt-border);margin-block-end:.8rem}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsDetailComponent, selector: "et-query-devtools-detail", inputs: ["sel"] }], encapsulation: i0.ViewEncapsulation.None }); }
2340
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2341
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsDrawerComponent, isStandalone: true, selector: "et-query-devtools-drawer", inputs: { sel: { classPropertyName: "sel", publicName: "sel", isSignal: true, isRequired: false, transformFunction: null }, container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { dismiss: "dismiss" }, ngImport: i0, template: "@if (sel(); as selected) {\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'drawer', container: container() })\"\n (dblclick)=\"host.resetPaneSize('drawer')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"dismiss.emit()\"\n class=\"et-query-devtools-btn et-query-devtools-icon-btn\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <et-query-devtools-detail [sel]=\"selected\" />\n </aside>\n}\n", styles: ["@layer components{et-query-devtools-drawer{display:contents}.et-query-devtools-drawer{flex:0 0 var(--_et-qdt-drawer-width, 42%);min-inline-size:var(--_et-qdt-drawer-width, 26rem);max-inline-size:var(--_et-qdt-drawer-width, 48rem);overflow-y:auto;padding:0 1rem 1rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-drawer-head{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding:.8rem 0;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg);color:var(--_et-qdt-muted);border-block-end:1px solid var(--_et-qdt-border);margin-block-end:.8rem}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsDetailComponent, selector: "et-query-devtools-detail", inputs: ["sel"] }], encapsulation: i0.ViewEncapsulation.None }); }
2136
2342
  }
2137
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDrawerComponent, decorators: [{
2343
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsDrawerComponent, decorators: [{
2138
2344
  type: Component,
2139
2345
  args: [{ selector: 'et-query-devtools-drawer', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDetailComponent], template: "@if (sel(); as selected) {\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'drawer', container: container() })\"\n (dblclick)=\"host.resetPaneSize('drawer')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button\n (click)=\"dismiss.emit()\"\n class=\"et-query-devtools-btn et-query-devtools-icon-btn\"\n type=\"button\"\n title=\"Close detail\"\n >\n \u2715\n </button>\n </div>\n <et-query-devtools-detail [sel]=\"selected\" />\n </aside>\n}\n", styles: ["@layer components{et-query-devtools-drawer{display:contents}.et-query-devtools-drawer{flex:0 0 var(--_et-qdt-drawer-width, 42%);min-inline-size:var(--_et-qdt-drawer-width, 26rem);max-inline-size:var(--_et-qdt-drawer-width, 48rem);overflow-y:auto;padding:0 1rem 1rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-drawer-head{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding:.8rem 0;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg);color:var(--_et-qdt-muted);border-block-end:1px solid var(--_et-qdt-border);margin-block-end:.8rem}}\n"] }]
2140
2346
  }], propDecorators: { sel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sel", required: false }] }], container: [{ type: i0.Input, args: [{ isSignal: true, alias: "container", required: true }] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }] } });
@@ -2157,10 +2363,7 @@ class QueryDevtoolsEventsTabComponent {
2157
2363
  }, /* @ts-ignore */
2158
2364
  ...(ngDevMode ? [{ debugName: "filteredEvents" }] : /* istanbul ignore next */ []));
2159
2365
  /** The clients the event log has rows from, as its picker offers them. */
2160
- this.eventClients = computed(() => {
2161
- const names = new Set(this.host.repositories().map(({ name, baseUrl }) => baseUrl || name));
2162
- return Array.from(names).sort();
2163
- }, /* @ts-ignore */
2366
+ this.eventClients = computed(() => queryDevtoolsEventClients(this.host.eventLog(), this.host.eventClient()), /* @ts-ignore */
2164
2367
  ...(ngDevMode ? [{ debugName: "eventClients" }] : /* istanbul ignore next */ []));
2165
2368
  this.isEventLogNarrowed = computed(() => !!this.host.eventClient() || this.host.eventErrorsOnly(), /* @ts-ignore */
2166
2369
  ...(ngDevMode ? [{ debugName: "isEventLogNarrowed" }] : /* istanbul ignore next */ []));
@@ -2208,12 +2411,12 @@ class QueryDevtoolsEventsTabComponent {
2208
2411
  return `dropped · ${DESTROY_CAUSE_LABELS[event.destroyCause ?? 'unbind']}`;
2209
2412
  return event.type === 'request-error' ? `error ${event.status}` : 'success';
2210
2413
  }
2211
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsEventsTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2212
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsEventsTabComponent, isStandalone: true, selector: "et-query-devtools-events-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (eventClients().length > 1) {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #eventClientSelect\n [value]=\"host.eventClient() ?? ''\"\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n </label>\n }\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-facet--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"toggleEventErrorsOnly()\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n title=\"Show only the requests that failed\"\n >\n Errors only\n <span class=\"et-query-devtools-facet-count\">{{ host.tabBadges().events.errors }}</span>\n </button>\n <span class=\"et-query-devtools-count\">\n @if (isEventLogNarrowed()) {\n {{ filteredEvents().length }} of {{ host.eventLog().length }} events\n } @else {\n {{ host.eventLog().length }} events\n }\n </span>\n @if (isEventLogNarrowed()) {\n <button\n (click)=\"clearEventFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the client scope and the errors-only toggle\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\" type=\"button\">\n Clear\n </button>\n </div>\n </div>\n <div class=\"et-query-devtools-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th class=\"et-query-devtools-cell-origin\">Base URL</th>\n <th>Type</th>\n <th>Request</th>\n <th>Duration</th>\n <th>Size</th>\n </tr>\n </thead>\n <tbody>\n @for (event of filteredEvents(); track event.id) {\n <tr\n [attr.data-status]=\"eventRowStatus(event)\"\n [class.et-query-devtools-row--active]=\"!!event.queryId && host.eventSelectedQueryId() === event.queryId\"\n >\n <td>{{ host.formatTime(event.timestamp) }}</td>\n <td [title]=\"event.client\" class=\"et-query-devtools-mono et-query-devtools-cell-origin\">\n {{ event.client }}\n </td>\n <td>\n {{ eventTypeLabel(event) }}\n @if (event.isSecure) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">secure</span>\n }\n </td>\n @if (event.cause; as cause) {\n <td>\n <span class=\"et-query-devtools-url\">{{ host.causeLabel(cause) }}</span>\n @if (event.refreshed?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (refreshed of event.refreshed; track $index) {\n @if (\n canOpenEventRow(refreshed.queryIds[0] ?? null) ? refreshed.queryIds[0] : null;\n as queryId\n ) {\n <button\n (click)=\"host.eventSelectedQueryId.set(queryId)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open {{ refreshed.method }} {{ refreshed.path }}\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n <!-- One request, several queries on the same cache key. Spelling the count\n out beats repeating an identical row per consumer. -->\n @if (refreshed.queryIds.length > 1) {\n <small>\u00D7{{ refreshed.queryIds.length }}</small>\n }\n </button>\n } @else {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No registered query holds this cache entry any more\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n </span>\n }\n }\n </div>\n } @else {\n <span class=\"et-query-devtools-hint\">matched no query in use</span>\n }\n </td>\n } @else if (event.url; as url) {\n <td>\n @if (canOpenEventRow(event.queryId)) {\n <button\n (click)=\"selectEventRow(event)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open {{ event.method }} {{ url }}\"\n >\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </button>\n } @else {\n <span [title]=\"event.method + ' ' + url\">\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n <td>{{ host.formatDuration(event.durationMs) }}</td>\n <td>{{ event.bytes === null ? '\u2014' : host.formatTransferred(event.bytes, event.isEstimatedBytes) }}</td>\n </tr>\n } @empty {\n <tr>\n @if (host.eventLog().length) {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">\n No event matches the filter \u00B7 {{ host.eventLog().length }} in the log.\n </td>\n } @else {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">No events captured yet.</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.eventSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.eventSelectedQueryId.set(null)\"\n />\n</div>\n", dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }], encapsulation: i0.ViewEncapsulation.None }); }
2414
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsEventsTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2415
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsEventsTabComponent, isStandalone: true, selector: "et-query-devtools-events-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (eventClients().length > 1) {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n @let pickedClient = host.eventClient();\n <select\n #eventClientSelect\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option [selected]=\"!pickedClient\" value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\" [selected]=\"name === pickedClient\">{{ name }}</option>\n }\n </select>\n </label>\n }\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-facet--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"toggleEventErrorsOnly()\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n title=\"Show only the requests that failed\"\n >\n Errors only\n <span class=\"et-query-devtools-facet-count\">{{ host.tabBadges().events.errors }}</span>\n </button>\n <span class=\"et-query-devtools-count\">\n @if (isEventLogNarrowed()) {\n {{ filteredEvents().length }} of {{ host.eventLog().length }} events\n } @else {\n {{ host.eventLog().length }} events\n }\n </span>\n @if (isEventLogNarrowed()) {\n <button\n (click)=\"clearEventFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the client scope and the errors-only toggle\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\" type=\"button\">\n Clear\n </button>\n </div>\n </div>\n <div class=\"et-query-devtools-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th class=\"et-query-devtools-cell-origin\">Base URL</th>\n <th>Type</th>\n <th>Request</th>\n <th>Duration</th>\n <th>Size</th>\n </tr>\n </thead>\n <tbody>\n @for (event of filteredEvents(); track event.id) {\n <tr\n [attr.data-status]=\"eventRowStatus(event)\"\n [class.et-query-devtools-row--active]=\"!!event.queryId && host.eventSelectedQueryId() === event.queryId\"\n >\n <td>{{ host.formatTime(event.timestamp) }}</td>\n <td [title]=\"event.client\" class=\"et-query-devtools-mono et-query-devtools-cell-origin\">\n {{ event.client }}\n </td>\n <td>\n {{ eventTypeLabel(event) }}\n @if (event.isSecure) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">secure</span>\n }\n </td>\n @if (event.cause; as cause) {\n <td>\n <span class=\"et-query-devtools-url\">{{ host.causeLabel(cause) }}</span>\n @if (event.refreshed?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (refreshed of event.refreshed; track $index) {\n @if (\n canOpenEventRow(refreshed.queryIds[0] ?? null) ? refreshed.queryIds[0] : null;\n as queryId\n ) {\n <button\n (click)=\"host.eventSelectedQueryId.set(queryId)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open {{ refreshed.method }} {{ refreshed.path }}\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n <!-- One request, several queries on the same cache key. Spelling the count\n out beats repeating an identical row per consumer. -->\n @if (refreshed.queryIds.length > 1) {\n <small>\u00D7{{ refreshed.queryIds.length }}</small>\n }\n </button>\n } @else {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No registered query holds this cache entry any more\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n </span>\n }\n }\n </div>\n } @else {\n <span class=\"et-query-devtools-hint\">matched no query in use</span>\n }\n </td>\n } @else if (event.url; as url) {\n <td>\n @if (canOpenEventRow(event.queryId)) {\n <button\n (click)=\"selectEventRow(event)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open {{ event.method }} {{ url }}\"\n >\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </button>\n } @else {\n <span [title]=\"event.method + ' ' + url\">\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n <td>{{ host.formatDuration(event.durationMs) }}</td>\n <td>{{ event.bytes === null ? '\u2014' : host.formatTransferred(event.bytes, event.isEstimatedBytes) }}</td>\n </tr>\n } @empty {\n <tr>\n @if (host.eventLog().length) {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">\n No event matches the filter \u00B7 {{ host.eventLog().length }} in the log.\n </td>\n } @else {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">No events captured yet.</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.eventSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.eventSelectedQueryId.set(null)\"\n />\n</div>\n", dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }], encapsulation: i0.ViewEncapsulation.None }); }
2213
2416
  }
2214
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsEventsTabComponent, decorators: [{
2417
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsEventsTabComponent, decorators: [{
2215
2418
  type: Component,
2216
- args: [{ selector: 'et-query-devtools-events-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDrawerComponent], template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (eventClients().length > 1) {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #eventClientSelect\n [value]=\"host.eventClient() ?? ''\"\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n </label>\n }\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-facet--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"toggleEventErrorsOnly()\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n title=\"Show only the requests that failed\"\n >\n Errors only\n <span class=\"et-query-devtools-facet-count\">{{ host.tabBadges().events.errors }}</span>\n </button>\n <span class=\"et-query-devtools-count\">\n @if (isEventLogNarrowed()) {\n {{ filteredEvents().length }} of {{ host.eventLog().length }} events\n } @else {\n {{ host.eventLog().length }} events\n }\n </span>\n @if (isEventLogNarrowed()) {\n <button\n (click)=\"clearEventFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the client scope and the errors-only toggle\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\" type=\"button\">\n Clear\n </button>\n </div>\n </div>\n <div class=\"et-query-devtools-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th class=\"et-query-devtools-cell-origin\">Base URL</th>\n <th>Type</th>\n <th>Request</th>\n <th>Duration</th>\n <th>Size</th>\n </tr>\n </thead>\n <tbody>\n @for (event of filteredEvents(); track event.id) {\n <tr\n [attr.data-status]=\"eventRowStatus(event)\"\n [class.et-query-devtools-row--active]=\"!!event.queryId && host.eventSelectedQueryId() === event.queryId\"\n >\n <td>{{ host.formatTime(event.timestamp) }}</td>\n <td [title]=\"event.client\" class=\"et-query-devtools-mono et-query-devtools-cell-origin\">\n {{ event.client }}\n </td>\n <td>\n {{ eventTypeLabel(event) }}\n @if (event.isSecure) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">secure</span>\n }\n </td>\n @if (event.cause; as cause) {\n <td>\n <span class=\"et-query-devtools-url\">{{ host.causeLabel(cause) }}</span>\n @if (event.refreshed?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (refreshed of event.refreshed; track $index) {\n @if (\n canOpenEventRow(refreshed.queryIds[0] ?? null) ? refreshed.queryIds[0] : null;\n as queryId\n ) {\n <button\n (click)=\"host.eventSelectedQueryId.set(queryId)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open {{ refreshed.method }} {{ refreshed.path }}\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n <!-- One request, several queries on the same cache key. Spelling the count\n out beats repeating an identical row per consumer. -->\n @if (refreshed.queryIds.length > 1) {\n <small>\u00D7{{ refreshed.queryIds.length }}</small>\n }\n </button>\n } @else {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No registered query holds this cache entry any more\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n </span>\n }\n }\n </div>\n } @else {\n <span class=\"et-query-devtools-hint\">matched no query in use</span>\n }\n </td>\n } @else if (event.url; as url) {\n <td>\n @if (canOpenEventRow(event.queryId)) {\n <button\n (click)=\"selectEventRow(event)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open {{ event.method }} {{ url }}\"\n >\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </button>\n } @else {\n <span [title]=\"event.method + ' ' + url\">\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n <td>{{ host.formatDuration(event.durationMs) }}</td>\n <td>{{ event.bytes === null ? '\u2014' : host.formatTransferred(event.bytes, event.isEstimatedBytes) }}</td>\n </tr>\n } @empty {\n <tr>\n @if (host.eventLog().length) {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">\n No event matches the filter \u00B7 {{ host.eventLog().length }} in the log.\n </td>\n } @else {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">No events captured yet.</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.eventSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.eventSelectedQueryId.set(null)\"\n />\n</div>\n" }]
2419
+ args: [{ selector: 'et-query-devtools-events-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDrawerComponent], template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (eventClients().length > 1) {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n @let pickedClient = host.eventClient();\n <select\n #eventClientSelect\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option [selected]=\"!pickedClient\" value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\" [selected]=\"name === pickedClient\">{{ name }}</option>\n }\n </select>\n </label>\n }\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-facet--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"toggleEventErrorsOnly()\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n title=\"Show only the requests that failed\"\n >\n Errors only\n <span class=\"et-query-devtools-facet-count\">{{ host.tabBadges().events.errors }}</span>\n </button>\n <span class=\"et-query-devtools-count\">\n @if (isEventLogNarrowed()) {\n {{ filteredEvents().length }} of {{ host.eventLog().length }} events\n } @else {\n {{ host.eventLog().length }} events\n }\n </span>\n @if (isEventLogNarrowed()) {\n <button\n (click)=\"clearEventFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the client scope and the errors-only toggle\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\" type=\"button\">\n Clear\n </button>\n </div>\n </div>\n <div class=\"et-query-devtools-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th class=\"et-query-devtools-cell-origin\">Base URL</th>\n <th>Type</th>\n <th>Request</th>\n <th>Duration</th>\n <th>Size</th>\n </tr>\n </thead>\n <tbody>\n @for (event of filteredEvents(); track event.id) {\n <tr\n [attr.data-status]=\"eventRowStatus(event)\"\n [class.et-query-devtools-row--active]=\"!!event.queryId && host.eventSelectedQueryId() === event.queryId\"\n >\n <td>{{ host.formatTime(event.timestamp) }}</td>\n <td [title]=\"event.client\" class=\"et-query-devtools-mono et-query-devtools-cell-origin\">\n {{ event.client }}\n </td>\n <td>\n {{ eventTypeLabel(event) }}\n @if (event.isSecure) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">secure</span>\n }\n </td>\n @if (event.cause; as cause) {\n <td>\n <span class=\"et-query-devtools-url\">{{ host.causeLabel(cause) }}</span>\n @if (event.refreshed?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (refreshed of event.refreshed; track $index) {\n @if (\n canOpenEventRow(refreshed.queryIds[0] ?? null) ? refreshed.queryIds[0] : null;\n as queryId\n ) {\n <button\n (click)=\"host.eventSelectedQueryId.set(queryId)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open {{ refreshed.method }} {{ refreshed.path }}\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n <!-- One request, several queries on the same cache key. Spelling the count\n out beats repeating an identical row per consumer. -->\n @if (refreshed.queryIds.length > 1) {\n <small>\u00D7{{ refreshed.queryIds.length }}</small>\n }\n </button>\n } @else {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No registered query holds this cache entry any more\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n </span>\n }\n }\n </div>\n } @else {\n <span class=\"et-query-devtools-hint\">matched no query in use</span>\n }\n </td>\n } @else if (event.url; as url) {\n <td>\n @if (canOpenEventRow(event.queryId)) {\n <button\n (click)=\"selectEventRow(event)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open {{ event.method }} {{ url }}\"\n >\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </button>\n } @else {\n <span [title]=\"event.method + ' ' + url\">\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n <td>{{ host.formatDuration(event.durationMs) }}</td>\n <td>{{ event.bytes === null ? '\u2014' : host.formatTransferred(event.bytes, event.isEstimatedBytes) }}</td>\n </tr>\n } @empty {\n <tr>\n @if (host.eventLog().length) {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">\n No event matches the filter \u00B7 {{ host.eventLog().length }} in the log.\n </td>\n } @else {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">No events captured yet.</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.eventSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.eventSelectedQueryId.set(null)\"\n />\n</div>\n" }]
2217
2420
  }] });
2218
2421
 
2219
2422
  /**
@@ -2295,10 +2498,10 @@ class QueryDevtoolsMockDesignerComponent {
2295
2498
  this.jsonError.set(error instanceof Error ? error.message : String(error));
2296
2499
  }
2297
2500
  }
2298
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsMockDesignerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2299
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsMockDesignerComponent, isStandalone: true, selector: "et-query-devtools-mock-designer", inputs: { body: { classPropertyName: "body", publicName: "body", isSignal: true, isRequired: false, transformFunction: null }, annotations: { classPropertyName: "annotations", publicName: "annotations", isSignal: true, isRequired: false, transformFunction: null }, notes: { classPropertyName: "notes", publicName: "notes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { save: "save", dismiss: "dismiss" }, ngImport: i0, template: "<div class=\"et-query-devtools-designer\">\n <div class=\"et-query-devtools-designer-head\">\n <span class=\"et-query-devtools-section\">Body</span>\n <div class=\"et-query-devtools-designer-modes\">\n <button\n [attr.aria-pressed]=\"mode() === 'design'\"\n [class.et-query-devtools-btn--primary]=\"mode() === 'design'\"\n (click)=\"showDesign()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Edit field by field, with the same menu the value explorer tampers with a real response through\"\n >\n Design\n </button>\n <button\n [attr.aria-pressed]=\"mode() === 'json'\"\n [class.et-query-devtools-btn--primary]=\"mode() === 'json'\"\n (click)=\"showJson()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Edit the whole body as raw JSON\"\n >\n JSON\n </button>\n </div>\n @if (editCount()) {\n <span class=\"et-query-devtools-count\">{{ editCount() }} {{ editCount() === 1 ? 'edit' : 'edits' }}</span>\n <button (click)=\"undoAll()\" class=\"et-query-devtools-btn\" type=\"button\">Undo all</button>\n }\n </div>\n\n @if (mode() === 'design') {\n <div class=\"et-query-devtools-designer-tree\">\n <et-query-devtools-json [value]=\"designed()\" [overrides]=\"recorder()\" [annotations]=\"annotations()\" />\n </div>\n } @else {\n <textarea\n #jsonInput\n [value]=\"jsonDraft()\"\n (input)=\"jsonDraft.set(jsonInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-mock-textarea\"\n rows=\"12\"\n spellcheck=\"false\"\n aria-label=\"The designed body as JSON\"\n ></textarea>\n }\n\n @if (jsonError(); as error) {\n <p class=\"et-query-devtools-settings-warn\">{{ error }}</p>\n }\n\n @if (notes().length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of notes(); track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n\n <div class=\"et-query-devtools-mock-head\">\n <button (click)=\"apply()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">Apply</button>\n <button (click)=\"dismiss.emit()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-designer{padding-block-start:.4rem}.et-query-devtools-designer-head{display:flex;align-items:center;flex-wrap:wrap;gap:.6rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-designer-modes{display:flex;gap:.2rem}.et-query-devtools-designer-tree{max-block-size:32rem;overflow:auto;padding:.4rem .6rem;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius-sm)}.et-query-devtools-designer-notes{margin:.4rem 0 0;padding-inline-start:1.6rem;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"], dependencies: [{ 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 }); }
2501
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsMockDesignerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2502
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsMockDesignerComponent, isStandalone: true, selector: "et-query-devtools-mock-designer", inputs: { body: { classPropertyName: "body", publicName: "body", isSignal: true, isRequired: false, transformFunction: null }, annotations: { classPropertyName: "annotations", publicName: "annotations", isSignal: true, isRequired: false, transformFunction: null }, notes: { classPropertyName: "notes", publicName: "notes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { save: "save", dismiss: "dismiss" }, ngImport: i0, template: "<div class=\"et-query-devtools-designer\">\n <div class=\"et-query-devtools-designer-head\">\n <span class=\"et-query-devtools-section\">Body</span>\n <div class=\"et-query-devtools-designer-modes\">\n <button\n [attr.aria-pressed]=\"mode() === 'design'\"\n [class.et-query-devtools-btn--primary]=\"mode() === 'design'\"\n (click)=\"showDesign()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Edit field by field, with the same menu the value explorer tampers with a real response through\"\n >\n Design\n </button>\n <button\n [attr.aria-pressed]=\"mode() === 'json'\"\n [class.et-query-devtools-btn--primary]=\"mode() === 'json'\"\n (click)=\"showJson()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Edit the whole body as raw JSON\"\n >\n JSON\n </button>\n </div>\n @if (editCount()) {\n <span class=\"et-query-devtools-count\">{{ editCount() }} {{ editCount() === 1 ? 'edit' : 'edits' }}</span>\n <button (click)=\"undoAll()\" class=\"et-query-devtools-btn\" type=\"button\">Undo all</button>\n }\n </div>\n\n @if (mode() === 'design') {\n <div class=\"et-query-devtools-designer-tree\">\n <et-query-devtools-json [value]=\"designed()\" [overrides]=\"recorder()\" [annotations]=\"annotations()\" />\n </div>\n } @else {\n <textarea\n #jsonInput\n [value]=\"jsonDraft()\"\n (input)=\"jsonDraft.set(jsonInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-mock-textarea\"\n rows=\"12\"\n spellcheck=\"false\"\n aria-label=\"The designed body as JSON\"\n ></textarea>\n }\n\n @if (jsonError(); as error) {\n <p class=\"et-query-devtools-settings-warn\">{{ error }}</p>\n }\n\n @if (notes().length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of notes(); track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n\n <div class=\"et-query-devtools-mock-head\">\n <button (click)=\"apply()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">Apply</button>\n <button (click)=\"dismiss.emit()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-designer{padding-block-start:.4rem}.et-query-devtools-designer-head{display:flex;align-items:center;flex-wrap:wrap;gap:.6rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-designer-modes{display:flex;gap:.2rem}.et-query-devtools-designer-tree{max-block-size:32rem;overflow:auto;padding:.4rem .6rem;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius-sm)}.et-query-devtools-designer-notes{margin:.4rem 0 0;padding-inline-start:1.6rem;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"], dependencies: [{ 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 }); }
2300
2503
  }
2301
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsMockDesignerComponent, decorators: [{
2504
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsMockDesignerComponent, decorators: [{
2302
2505
  type: Component,
2303
2506
  args: [{ selector: 'et-query-devtools-mock-designer', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsJsonComponent], template: "<div class=\"et-query-devtools-designer\">\n <div class=\"et-query-devtools-designer-head\">\n <span class=\"et-query-devtools-section\">Body</span>\n <div class=\"et-query-devtools-designer-modes\">\n <button\n [attr.aria-pressed]=\"mode() === 'design'\"\n [class.et-query-devtools-btn--primary]=\"mode() === 'design'\"\n (click)=\"showDesign()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Edit field by field, with the same menu the value explorer tampers with a real response through\"\n >\n Design\n </button>\n <button\n [attr.aria-pressed]=\"mode() === 'json'\"\n [class.et-query-devtools-btn--primary]=\"mode() === 'json'\"\n (click)=\"showJson()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Edit the whole body as raw JSON\"\n >\n JSON\n </button>\n </div>\n @if (editCount()) {\n <span class=\"et-query-devtools-count\">{{ editCount() }} {{ editCount() === 1 ? 'edit' : 'edits' }}</span>\n <button (click)=\"undoAll()\" class=\"et-query-devtools-btn\" type=\"button\">Undo all</button>\n }\n </div>\n\n @if (mode() === 'design') {\n <div class=\"et-query-devtools-designer-tree\">\n <et-query-devtools-json [value]=\"designed()\" [overrides]=\"recorder()\" [annotations]=\"annotations()\" />\n </div>\n } @else {\n <textarea\n #jsonInput\n [value]=\"jsonDraft()\"\n (input)=\"jsonDraft.set(jsonInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-mock-textarea\"\n rows=\"12\"\n spellcheck=\"false\"\n aria-label=\"The designed body as JSON\"\n ></textarea>\n }\n\n @if (jsonError(); as error) {\n <p class=\"et-query-devtools-settings-warn\">{{ error }}</p>\n }\n\n @if (notes().length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of notes(); track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n\n <div class=\"et-query-devtools-mock-head\">\n <button (click)=\"apply()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">Apply</button>\n <button (click)=\"dismiss.emit()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-designer{padding-block-start:.4rem}.et-query-devtools-designer-head{display:flex;align-items:center;flex-wrap:wrap;gap:.6rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-designer-modes{display:flex;gap:.2rem}.et-query-devtools-designer-tree{max-block-size:32rem;overflow:auto;padding:.4rem .6rem;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius-sm)}.et-query-devtools-designer-notes{margin:.4rem 0 0;padding-inline-start:1.6rem;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"] }]
2304
2507
  }], propDecorators: { body: [{ type: i0.Input, args: [{ isSignal: true, alias: "body", required: false }] }], annotations: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotations", required: false }] }], notes: [{ type: i0.Input, args: [{ isSignal: true, alias: "notes", required: false }] }], save: [{ type: i0.Output, args: ["save"] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }] } });
@@ -2321,7 +2524,10 @@ const DATE_TIME = /^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2
2321
2524
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
2322
2525
  const isPlainObject$3 = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
2323
2526
  /** `/matches/:id` is how a mock writes a route; a document writes it `/matches/{id}`. */
2324
- const toBracePattern = (pattern) => pattern.replace(/:([^/]+)/g, '{$1}');
2527
+ const toBracePattern = (pattern) => pattern
2528
+ .split('/')
2529
+ .map((segment) => (segment.startsWith(':') ? `{${segment.slice(1)}}` : segment))
2530
+ .join('/');
2325
2531
  const pathParamsOf = (pattern) => pattern
2326
2532
  .split('/')
2327
2533
  .filter((segment) => segment.startsWith(':'))
@@ -2593,10 +2799,20 @@ const referencedSchemas = (paths, schemas) => {
2593
2799
  return used;
2594
2800
  };
2595
2801
  const REF_PREFIX = '#/components/schemas/';
2596
- const collectRefNames = (value, into = new Set()) => {
2802
+ const newRefScan = () => ({ into: new Set(), seen: new WeakSet(), depth: 0 });
2803
+ const collectRefNames = (value, scan = newRefScan()) => {
2804
+ const { into, seen, depth } = scan;
2805
+ if (depth >= MAX_DEPTH$3 || !value || typeof value !== 'object')
2806
+ return into;
2807
+ // A dereferenced description is a graph, not a tree: without this, a schema that holds itself recurses
2808
+ // until the stack goes.
2809
+ if (seen.has(value))
2810
+ return into;
2811
+ seen.add(value);
2812
+ const next = { ...scan, depth: depth + 1 };
2597
2813
  if (Array.isArray(value)) {
2598
2814
  for (const entry of value)
2599
- collectRefNames(entry, into);
2815
+ collectRefNames(entry, next);
2600
2816
  return into;
2601
2817
  }
2602
2818
  if (!isPlainObject$3(value))
@@ -2606,7 +2822,7 @@ const collectRefNames = (value, into = new Set()) => {
2606
2822
  into.add(entry.slice(REF_PREFIX.length));
2607
2823
  continue;
2608
2824
  }
2609
- collectRefNames(entry, into);
2825
+ collectRefNames(entry, next);
2610
2826
  }
2611
2827
  return into;
2612
2828
  };
@@ -2619,6 +2835,8 @@ const MAX_DEPTH$2 = 8;
2619
2835
  const INDENT$1 = ' ';
2620
2836
  /** A key that can be written bare in a type literal; anything else is quoted. */
2621
2837
  const isSafeKey = (key) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key);
2838
+ /** The key as a type literal spells it: bare where it can be, single-quoted where it cannot. */
2839
+ const typeKey = (key) => (isSafeKey(key) ? key : `'${key.replace(/(['\\])/g, '\\$1')}'`);
2622
2840
  const isPlainObject$2 = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
2623
2841
  /**
2624
2842
  * The TypeScript type of one sample value. **Inferred from a single example**, so it says what that
@@ -2684,7 +2902,12 @@ const routeOf = (pattern, params) => {
2684
2902
  return `'${pattern}'`;
2685
2903
  const interpolated = pattern
2686
2904
  .split('/')
2687
- .map((segment) => (segment.startsWith(':') ? `\${p.${segment.slice(1)}}` : segment))
2905
+ .map((segment) => {
2906
+ if (!segment.startsWith(':'))
2907
+ return segment;
2908
+ const param = segment.slice(1);
2909
+ return isSafeKey(param) ? `\${p.${param}}` : `\${p[${typeKey(param)}]}`;
2910
+ })
2688
2911
  .join('/');
2689
2912
  return `(p) => \`${interpolated}\``;
2690
2913
  };
@@ -2706,10 +2929,12 @@ const buildQueryDefinitionSnippet = (options) => {
2706
2929
  const queryParams = [...new URLSearchParams(query)];
2707
2930
  const argFields = [`${INDENT$1}response: ${typeName}Response;`];
2708
2931
  if (params.length) {
2709
- argFields.push(`${INDENT$1}pathParams: { ${params.map((param) => `${param}: string`).join('; ')} };`);
2932
+ argFields.push(`${INDENT$1}pathParams: { ${params.map((param) => `${typeKey(param)}: string`).join('; ')} };`);
2710
2933
  }
2711
2934
  if (queryParams.length) {
2712
- const fields = queryParams.map(([key, value]) => `${key}: ${queryParamType(value)}`).join('; ');
2935
+ // A repeated key is one field, not two: `?tag=a&tag=b` is a duplicate identifier in a type literal.
2936
+ const byKey = new Map(queryParams.map(([key, value]) => [key, value]));
2937
+ const fields = Array.from(byKey, ([key, value]) => `${typeKey(key)}: ${queryParamType(value)}`).join('; ');
2713
2938
  argFields.push(`${INDENT$1}queryParams: { ${fields} };`);
2714
2939
  }
2715
2940
  const factory = `${method.toLowerCase()}Query`;
@@ -2733,7 +2958,7 @@ const buildQueryDefinitionSnippet = (options) => {
2733
2958
  * JSON export writes - it is a second spelling of one document, not a second document.
2734
2959
  */
2735
2960
  const INDENT = ' ';
2736
- /** How deep the tree is written before a branch is given up on, so a cyclic body cannot spin forever. */
2961
+ /** How deep the tree is written before a branch is given up on, so no document can overflow the stack. */
2737
2962
  const MAX_DEPTH$1 = 64;
2738
2963
  /**
2739
2964
  * A scalar YAML would read back as this exact string: it starts with a letter, `_` or `$`, so nothing
@@ -2766,6 +2991,13 @@ const blockScalar = (value, indent) => `|-\n${value
2766
2991
  .split('\n')
2767
2992
  .map((line) => (line ? `${indent}${line}` : ''))
2768
2993
  .join('\n')}`;
2994
+ /** What `JSON.stringify` would serialise in this value's place - a `Date` writes as its ISO string. */
2995
+ const jsonValueOf = (value) => {
2996
+ if (!value || typeof value !== 'object')
2997
+ return value;
2998
+ const toJson = value.toJSON;
2999
+ return typeof toJson === 'function' ? toJson.call(value) : value;
3000
+ };
2769
3001
  const isPlainObject$1 = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
2770
3002
  /** A scalar, or `null` when the value is one of the containers {@link write} handles itself. */
2771
3003
  const scalarOf = (value, indent) => {
@@ -2789,42 +3021,56 @@ const scalarOf = (value, indent) => {
2789
3021
  return null;
2790
3022
  return 'null';
2791
3023
  };
2792
- const write = (value, depth) => {
3024
+ const write = (input, state) => {
3025
+ const { depth, ancestors } = state;
2793
3026
  const indent = INDENT.repeat(depth);
3027
+ const value = jsonValueOf(input);
2794
3028
  const scalar = scalarOf(value, indent);
2795
3029
  if (scalar !== null)
2796
3030
  return scalar;
2797
3031
  if (depth >= MAX_DEPTH$1)
2798
3032
  return 'null';
3033
+ // Depth alone does not bound a cycle reachable through two keys: that branches, so 64 levels are 2^64
3034
+ // nodes rather than 64. Only refusing to re-enter an ancestor stops it.
3035
+ if (ancestors.has(value))
3036
+ return 'null';
3037
+ ancestors.add(value);
3038
+ const child = { depth: depth + 1, ancestors };
3039
+ let written;
2799
3040
  if (Array.isArray(value)) {
2800
- if (!value.length)
2801
- return '[]';
2802
- return `\n${value
2803
- .map((entry) => {
2804
- const written = write(entry, depth + 1);
2805
- // A container under a `-` continues on the dash line: the `- ` occupies the first two columns of
2806
- // its child's own indentation, so the child's first line has to shed exactly that much.
2807
- return `${indent}- ${written.startsWith('\n') ? written.slice(depth * 2 + INDENT.length + 1) : written}`;
2808
- })
2809
- .join('\n')}`;
2810
- }
2811
- const entries = Object.entries(value).filter(([, entry]) => entry !== undefined);
2812
- if (!entries.length)
2813
- return '{}';
2814
- return `\n${entries
2815
- .map(([key, entry]) => {
2816
- const written = write(entry, depth + 1);
2817
- // A block already leads with its own newline; only a scalar needs the space after the colon.
2818
- const rendered = written.startsWith('\n') ? written : ` ${written}`;
2819
- return `${indent}${isPlainSafe(key) ? key : JSON.stringify(key)}:${rendered}`;
2820
- })
2821
- .join('\n')}`;
3041
+ written = value.length
3042
+ ? `\n${value
3043
+ .map((entry) => {
3044
+ const item = write(entry, child);
3045
+ // A container under a `-` continues on the dash line: the `- ` occupies the first two columns
3046
+ // of its child's own indentation, so the child's first line has to shed exactly that much.
3047
+ return `${indent}- ${item.startsWith('\n') ? item.slice(depth * 2 + INDENT.length + 1) : item}`;
3048
+ })
3049
+ .join('\n')}`
3050
+ : '[]';
3051
+ }
3052
+ else {
3053
+ const entries = Object.entries(value).filter(([, entry]) => entry !== undefined);
3054
+ written = entries.length
3055
+ ? `\n${entries
3056
+ .map(([key, entry]) => {
3057
+ const item = write(entry, child);
3058
+ // A block already leads with its own newline; only a scalar needs the space after the colon.
3059
+ const rendered = item.startsWith('\n') ? item : ` ${item}`;
3060
+ return `${indent}${isPlainSafe(key) ? key : JSON.stringify(key)}:${rendered}`;
3061
+ })
3062
+ .join('\n')}`
3063
+ : '{}';
3064
+ }
3065
+ ancestors.delete(value);
3066
+ return written;
2822
3067
  };
2823
3068
  /**
2824
- * Writes a JSON-shaped tree as YAML. `undefined` members are dropped the way `JSON.stringify` drops
2825
- * them; anything that is not a plain object, array or primitive is written as `null`.
3069
+ * Writes a JSON-shaped tree as YAML. `undefined` members are dropped and a `toJSON()` carrier is written
3070
+ * through it, both the way `JSON.stringify` does; anything else that is not a plain object, array or
3071
+ * primitive is written as `null`, and a node that re-enters one of its own ancestors as `null` too.
2826
3072
  */
2827
- const toQueryDevtoolsYaml = (value) => `${write(value, 0).replace(/^\n/, '')}\n`;
3073
+ const toQueryDevtoolsYaml = (value) => `${write(value, { depth: 0, ancestors: new Set() }).replace(/^\n/, '')}\n`;
2828
3074
 
2829
3075
  const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
2830
3076
  const SEED_STYLES = [
@@ -2929,12 +3175,6 @@ class QueryDevtoolsMocksTabComponent {
2929
3175
  /** The named schema picked in the form, which is not the same thing as the one a body came from. */
2930
3176
  this.pickedSchema = signal('', /* @ts-ignore */
2931
3177
  ...(ngDevMode ? [{ debugName: "pickedSchema" }] : /* istanbul ignore next */ []));
2932
- /**
2933
- * The type annotations of each seeded body, by mock id. A mock only stores the *name* it was seeded
2934
- * from, so this is what labels a body the description could not name - for as long as the panel is open.
2935
- */
2936
- this.seededTypes = signal({}, /* @ts-ignore */
2937
- ...(ngDevMode ? [{ debugName: "seededTypes" }] : /* istanbul ignore next */ []));
2938
3178
  /** The mock whose body is open in the designer. */
2939
3179
  this.editingId = signal(null, /* @ts-ignore */
2940
3180
  ...(ngDevMode ? [{ debugName: "editingId" }] : /* istanbul ignore next */ []));
@@ -2944,20 +3184,28 @@ class QueryDevtoolsMocksTabComponent {
2944
3184
  /** The mock whose path item was last copied - a second button needs its own confirmation. */
2945
3185
  this.copiedSpecId = signal(null, /* @ts-ignore */
2946
3186
  ...(ngDevMode ? [{ debugName: "copiedSpecId" }] : /* istanbul ignore next */ []));
3187
+ this.copiedReset$ = new Subject();
2947
3188
  /** Which spelling the OpenAPI export is written in. YAML is what a specification repository takes. */
2948
3189
  this.specFormat = signal('yaml', /* @ts-ignore */
2949
3190
  ...(ngDevMode ? [{ debugName: "specFormat" }] : /* istanbul ignore next */ []));
2950
3191
  /** What the last export had to guess or could not resolve, shown under the library it came from. */
2951
3192
  this.specNotes = signal([], /* @ts-ignore */
2952
3193
  ...(ngDevMode ? [{ debugName: "specNotes" }] : /* istanbul ignore next */ []));
3194
+ /**
3195
+ * The library with every designed body measured. Measuring serializes the body, so it is kept out of
3196
+ * {@link rows} - which reads the registry, and would otherwise re-serialize every mock whenever a
3197
+ * query anywhere in the application registers or is destroyed.
3198
+ */
3199
+ this.measuredMocks = computed(() => queryDevtoolsMocks().map((mock) => ({ mock, bytes: measureQueryDevtoolsPayload({ body: mock.body }).bytes })), /* @ts-ignore */
3200
+ ...(ngDevMode ? [{ debugName: "measuredMocks" }] : /* istanbul ignore next */ []));
2953
3201
  this.rows = computed(() => {
2954
3202
  const armed = queryDevtoolsArmedMocks();
2955
3203
  const entries = this.host.queryEntries();
2956
- return queryDevtoolsMocks().map((mock) => ({
3204
+ return this.measuredMocks().map(({ mock, bytes }) => ({
2957
3205
  mock,
3206
+ bytes,
2958
3207
  armed: armed.has(mock.id),
2959
- bytes: measureQueryDevtoolsPayload({ body: mock.body }).bytes,
2960
- isSecure: entries.some((entry) => !!entry.meta.isSecure && this.idOf(entry) === mock.id),
3208
+ isSecure: entries.some((entry) => !!entry.meta.isSecure && this.idOf(entry, mock.query) === mock.id),
2961
3209
  }));
2962
3210
  }, /* @ts-ignore */
2963
3211
  ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
@@ -2973,7 +3221,7 @@ class QueryDevtoolsMocksTabComponent {
2973
3221
  const row = this.editingRow();
2974
3222
  if (!row)
2975
3223
  return null;
2976
- const seeded = this.seededTypes()[row.mock.id];
3224
+ const seeded = this.host.seededTypes()[row.mock.id];
2977
3225
  if (seeded)
2978
3226
  return seeded;
2979
3227
  const name = row.mock.schemaName;
@@ -3014,6 +3262,13 @@ class QueryDevtoolsMocksTabComponent {
3014
3262
  return [...rows.values()];
3015
3263
  }, /* @ts-ignore */
3016
3264
  ...(ngDevMode ? [{ debugName: "captureRows" }] : /* istanbul ignore next */ []));
3265
+ // Each copy restarts the tick countdown; switchMap drops the pending reset of the previous one.
3266
+ this.copiedReset$
3267
+ .pipe(switchMap(() => timer(QUERY_DEVTOOLS_COPIED_RESET_MS)), tap(() => {
3268
+ this.copiedId.set(null);
3269
+ this.copiedSpecId.set(null);
3270
+ }), takeUntilDestroyed())
3271
+ .subscribe();
3017
3272
  // A description is only worth fetching once someone is designing a mock against that client, so an
3018
3273
  // application that hands several in still ships each as its own lazy chunk, loaded one at a time.
3019
3274
  effect(() => {
@@ -3119,7 +3374,7 @@ class QueryDevtoolsMocksTabComponent {
3119
3374
  schemaName: seed?.schemaName ?? null,
3120
3375
  });
3121
3376
  if (seed)
3122
- this.seededTypes.update((current) => ({ ...current, [id]: seed.types }));
3377
+ this.host.seededTypes.update((current) => ({ ...current, [id]: seed.types }));
3123
3378
  this.closeDraft();
3124
3379
  this.editingId.set(id);
3125
3380
  }
@@ -3167,7 +3422,7 @@ class QueryDevtoolsMocksTabComponent {
3167
3422
  body: row.mock.body,
3168
3423
  });
3169
3424
  copyToClipboard(snippet)
3170
- .pipe(tap((ok) => this.copiedId.set(ok ? row.mock.id : null)))
3425
+ .pipe(tap((ok) => this.confirmCopy(this.copiedId, ok ? row.mock.id : null)))
3171
3426
  .subscribe();
3172
3427
  }
3173
3428
  /**
@@ -3180,7 +3435,7 @@ class QueryDevtoolsMocksTabComponent {
3180
3435
  const built = buildQueryDevtoolsOpenApiPathItem({ mocks: [row.mock], schemas: schemas.schemas });
3181
3436
  this.specNotes.set([...schemas.notes, ...built.notes]);
3182
3437
  copyToClipboard(this.write(built.document))
3183
- .pipe(tap((ok) => this.copiedSpecId.set(ok ? row.mock.id : null)))
3438
+ .pipe(tap((ok) => this.confirmCopy(this.copiedSpecId, ok ? row.mock.id : null)))
3184
3439
  .subscribe();
3185
3440
  }
3186
3441
  /**
@@ -3282,17 +3537,26 @@ class QueryDevtoolsMocksTabComponent {
3282
3537
  const count = Math.trunc(Number(value));
3283
3538
  return Number.isFinite(count) ? count : fallback;
3284
3539
  }
3285
- idOf(entry) {
3540
+ /**
3541
+ * The mock id a live query would carry. `query` has to be the declared query of the mock being matched
3542
+ * against: it is part of the id, and a registered query carries no declaration of its own.
3543
+ */
3544
+ confirmCopy(target, id) {
3545
+ target.set(id);
3546
+ this.copiedReset$.next();
3547
+ }
3548
+ idOf(entry, query = '') {
3286
3549
  return queryDevtoolsMockId({
3287
3550
  clientName: entry.meta.clientName ?? '',
3288
3551
  method: entry.meta.method ?? 'GET',
3289
3552
  pattern: entry.meta.route ?? '',
3553
+ query,
3290
3554
  });
3291
3555
  }
3292
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsMocksTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3293
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsMocksTabComponent, isStandalone: true, selector: "et-query-devtools-mocks-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-mocks-toolbar\">\n <p class=\"et-query-devtools-hint\">\n A mock replaces the request, so it answers a route that has never run - or does not exist yet. It bypasses the\n interceptor chain by construction, so a mocked secure route never exercises the token flow. The library is kept;\n whether a mock is <strong>armed</strong> is not, so a reload always stops serving them. GraphQL queries are not\n offered: they all POST one route, so matching them needs the document rather than the path.\n </p>\n <div class=\"et-query-devtools-settings-controls\">\n @if (rows().length && armedCount() < rows().length) {\n <button\n (click)=\"ARM_ALL()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Serve every mock in the library - the whole screen off the network at once\"\n >\n Arm all ({{ rows().length }})\n </button>\n }\n @if (armedCount()) {\n <button (click)=\"DISARM_ALL()\" class=\"et-query-devtools-btn\" type=\"button\">\n Disarm all ({{ armedCount() }})\n </button>\n }\n <button (click)=\"openDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n <span aria-hidden=\"true\">\uFF0B</span>\n New mock\n </button>\n </div>\n </div>\n\n @if (draft(); as form) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>New mock</strong>\n <span class=\"et-query-devtools-count\">for any route, whether a query has ever called it or not</span>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select #clientSelect (change)=\"pickClient(clientSelect.value)\" class=\"et-query-devtools-select\">\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\" [selected]=\"name === form.clientName\">{{ name }}</option>\n }\n </select>\n <small>a mock answers one client, matched by name</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Method</span>\n <select #methodSelect (change)=\"patchDraft({ method: methodSelect.value })\" class=\"et-query-devtools-select\">\n @for (method of METHODS; track method) {\n <option [value]=\"method\" [selected]=\"method === form.method\">{{ method }}</option>\n }\n </select>\n <small>matched exactly</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Path</span>\n <input\n #patternInput\n [value]=\"form.pattern\"\n (input)=\"patchDraft({ pattern: patternInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"/posts/:id/comments\"\n />\n <small><code>:name</code> matches any one segment, so the route is mocked rather than one URL</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Query</span>\n <input\n #queryInput\n [value]=\"form.query\"\n (input)=\"patchDraft({ query: queryInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"page=2&limit=10\"\n />\n <small>\n parameters the request must carry; anything else it asks for is ignored. Empty answers whatever the query\n string is - and a mock naming parameters wins over one that does not\n </small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusDraftInput\n [value]=\"form.status\"\n (input)=\"patchDraft({ status: statusDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyDraftInput\n [value]=\"form.latencyMs\"\n (input)=\"patchDraft({ latencyMs: latencyDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles</small>\n </label>\n </div>\n\n @if (hasSchema()) {\n <div class=\"et-query-devtools-seed\">\n <span class=\"et-query-devtools-section\">Seed</span>\n @if (isSchemaReady()) {\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [options]=\"routeOptions()\"\n (pickOption)=\"pickRoute($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n pickOnly\n >\n <input etSelectSearch placeholder=\"search the routes it declares\u2026\" />\n </et-select>\n <button\n (click)=\"seedFromDraftRoute()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Generate a body from what the description says this path returns\"\n >\n From this path\n </button>\n </div>\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [value]=\"pickedSchema()\"\n [options]=\"schemaNameOptions()\"\n (valueChange)=\"pickSchema($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n >\n <input etSelectSearch placeholder=\"search its named types\u2026\" />\n </et-select>\n <button\n [disabled]=\"!pickedSchema()\"\n (click)=\"seedFromSchema()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n From this type\n </button>\n </div>\n <select\n #stylePick\n (change)=\"pickSeedStyle(stylePick.value)\"\n class=\"et-query-devtools-select\"\n title=\"How lifelike the generated values are\"\n >\n @for (style of SEED_STYLES; track style.value) {\n <option [value]=\"style.value\" [selected]=\"style.value === seedStyle()\">{{ style.label }}</option>\n }\n </select>\n <small class=\"et-query-devtools-hint et-query-devtools-seed-hint\">{{ seedStyleHint() }}</small>\n } @else if (schemaError(); as error) {\n <span class=\"et-query-devtools-settings-warn\">The API description could not be loaded: {{ error }}</span>\n } @else {\n <span class=\"et-query-devtools-count\">Loading the API description\u2026</span>\n }\n </div>\n }\n\n <label class=\"et-query-devtools-mock-body\">\n <span class=\"et-query-devtools-section\">Body</span>\n <textarea\n #bodyInput\n [value]=\"form.body\"\n (input)=\"patchDraft({ body: bodyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-mock-textarea\"\n rows=\"8\"\n spellcheck=\"false\"\n ></textarea>\n </label>\n\n @if (draftSeed(); as seed) {\n <p class=\"et-query-devtools-hint\">\n Generated from the API description{{ seed.schemaName ? ' as ' + seed.schemaName : '' }} - a starting point,\n not the contract. Saving opens the designer on it.\n </p>\n @if (seed.notes.length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of seed.notes; track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n }\n\n @if (draftError(); as error) {\n <p class=\"et-query-devtools-settings-warn\">{{ error }}</p>\n }\n\n <div class=\"et-query-devtools-mock-head\">\n <button (click)=\"saveDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n Save to the library\n </button>\n <button (click)=\"closeDraft()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Designed</strong>\n <span class=\"et-query-devtools-count\">{{ rows().length }} in the library</span>\n @if (rows().length) {\n <div class=\"et-query-devtools-spec-export\">\n <select #formatPick (change)=\"pickSpecFormat(formatPick.value)\" class=\"et-query-devtools-select\">\n @for (format of SPEC_FORMATS; track format.value) {\n <option [value]=\"format.value\" [selected]=\"format.value === specFormat()\">{{ format.label }}</option>\n }\n </select>\n <button\n (click)=\"exportLibrary()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every designed route as one OpenAPI 3.1 document - the response schemas inferred from the designed bodies, each body as an example\"\n >\n <span aria-hidden=\"true\">\u21E9</span>\n Export OpenAPI\n </button>\n </div>\n }\n </div>\n\n @for (row of rows(); track row.mock.id) {\n <div [class.et-query-devtools-mock-row--armed]=\"row.armed\" class=\"et-query-devtools-mock-row\">\n <div class=\"et-query-devtools-mock-head\">\n <button\n [class.et-query-devtools-btn--tampered]=\"row.armed\"\n [attr.aria-pressed]=\"row.armed\"\n (click)=\"toggleArmed(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"{{ row.armed ? 'Stop serving this response' : 'Serve this response instead of the request' }}\"\n >\n {{ row.armed ? 'Armed' : 'Arm' }}\n </button>\n <span class=\"et-query-devtools-method\">{{ row.mock.method }}</span>\n <span class=\"et-query-devtools-route et-query-devtools-mono\">{{ row.mock.pattern }}</span>\n @if (row.mock.query) {\n <span class=\"et-query-devtools-url\">?{{ row.mock.query }}</span>\n }\n <span class=\"et-query-devtools-url\">{{ row.mock.clientName }}</span>\n @if (row.mock.capturedAt) {\n <span class=\"et-query-devtools-chip\">captured</span>\n }\n @if (row.mock.schemaName; as schemaName) {\n <span class=\"et-query-devtools-chip\" title=\"Seeded from the API description\">{{ schemaName }}</span>\n }\n @if (row.isSecure) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"A mock sends nothing, so no token is attached and the auth flow is never exercised\"\n >\n no auth\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"editBody(row)\" class=\"et-query-devtools-btn\" type=\"button\">Body</button>\n <button\n (click)=\"copyDefinition(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as a @ethlete/query definition - the response type inferred from the body, the args contract and the creator call\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedId() === row.mock.id ? 'Copied' : 'TS' }}\n </button>\n <button\n (click)=\"copyPathItem(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as an OpenAPI paths entry, ready to paste under an existing description\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedSpecId() === row.mock.id ? 'Copied' : 'OAS' }}\n </button>\n <button (click)=\"remove(row)\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Delete\n </button>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusInput\n [value]=\"row.mock.status\"\n (change)=\"setStatus(row, statusInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error, body and all</small>\n </label>\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"row.mock.latencyMs\"\n (change)=\"setLatency(row, latencyInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles, so the route still has a loading state</small>\n </label>\n </div>\n\n @if (editingId() === row.mock.id) {\n <et-query-devtools-mock-designer\n [body]=\"row.mock.body\"\n [annotations]=\"editingAnnotations()\"\n (save)=\"saveBody(row, $event)\"\n (dismiss)=\"cancelBody()\"\n />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">\n Nothing designed yet. <strong>New mock</strong> writes one from scratch; capturing below seeds one from\n something that actually happened.\n </p>\n }\n\n @if (specNotes().length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of specNotes(); track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Capture</strong>\n <span class=\"et-query-devtools-count\">live queries holding a response</span>\n </div>\n\n @for (row of captureRows(); track row.entry.id) {\n <div class=\"et-query-devtools-mock-head\">\n <span class=\"et-query-devtools-method\">{{ row.method }}</span>\n <button\n (click)=\"host.selectQuery(row.entry.id)\"\n class=\"et-query-devtools-route et-query-devtools-mono et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open this query\"\n >\n {{ row.pattern }}\n </button>\n <span class=\"et-query-devtools-url\">{{ row.clientName }}</span>\n @if (row.shared > 1) {\n <span class=\"et-query-devtools-count\" title=\"A mock answers the route, so it answers all of them\">\n {{ row.shared }} queries\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"capture(row)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ row.exists ? 'Re-capture' : 'Capture' }}\n </button>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No live query has a response yet.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-mocks-toolbar{align-items:flex-start}.et-query-devtools-mock-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-mock-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-mock-row--armed{background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-mock-head{display:flex;align-items:center;flex-wrap:wrap;gap:.6rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-mock-fields,.et-query-devtools-seed,.et-query-devtools-mock-body{max-inline-size:96rem}.et-query-devtools-mock-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(38rem,1fr));align-items:start;gap:.4rem 1.6rem;padding-block-start:.4rem}.et-query-devtools-mock-fields .et-query-devtools-field{display:grid;grid-template-columns:7rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-mock-fields .et-query-devtools-select{justify-self:start;max-inline-size:100%}.et-query-devtools-mock-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-mock-fields .et-query-devtools-field--wide{grid-column:1 / -1;grid-template-columns:7rem minmax(0,1fr)}.et-query-devtools-spec-export{display:flex;align-items:center;gap:.6rem;margin-inline-start:auto}.et-query-devtools-seed{display:flex;align-items:center;flex-wrap:wrap;gap:.4rem 2.4rem;padding-block-start:.4rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-seed-pick{display:flex;align-items:center;gap:.2rem}.et-query-devtools-seed .et-query-devtools-select{max-inline-size:32rem}.et-query-devtools-combobox{flex:0 1 auto;inline-size:32rem;min-block-size:var(--_et-qdt-control-h);box-sizing:border-box;background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .6rem;font-size:1.15rem}.et-query-devtools-combobox input{background:none;border:0;padding:0;color:inherit;font:inherit}.et-query-devtools-seed-hint{flex-basis:100%;margin:0}.et-query-devtools-mock-body{display:block;padding-block-start:.4rem}.et-query-devtools-mock-textarea{display:block;inline-size:100%;resize:vertical;font-family:inherit;font-size:1.15rem;line-height:1.5;tab-size:2}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsMockDesignerComponent, selector: "et-query-devtools-mock-designer", inputs: ["body", "annotations", "notes"], outputs: ["save", "dismiss"] }, { kind: "component", type: SelectComponent, selector: "et-select", inputs: ["loadMoreLabel", "addNewLabel", "createLabel", "clearable", "clearLabel"] }, { kind: "directive", type: SelectSearchDirective, selector: "input[etSelectSearch]", inputs: ["query"], outputs: ["queryChange"], exportAs: ["etSelectSearch"] }], encapsulation: i0.ViewEncapsulation.None }); }
3556
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsMocksTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3557
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsMocksTabComponent, isStandalone: true, selector: "et-query-devtools-mocks-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-mocks-toolbar\">\n <p class=\"et-query-devtools-hint\">\n A mock replaces the request, so it answers a route that has never run - or does not exist yet. It bypasses the\n interceptor chain by construction, so a mocked secure route never exercises the token flow. The library is kept;\n whether a mock is <strong>armed</strong> is not, so a reload always stops serving them. GraphQL queries are not\n offered: they all POST one route, so matching them needs the document rather than the path.\n </p>\n <div class=\"et-query-devtools-settings-controls\">\n @if (rows().length && armedCount() < rows().length) {\n <button\n (click)=\"ARM_ALL()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Serve every mock in the library - the whole screen off the network at once\"\n >\n Arm all ({{ rows().length }})\n </button>\n }\n @if (armedCount()) {\n <button (click)=\"DISARM_ALL()\" class=\"et-query-devtools-btn\" type=\"button\">\n Disarm all ({{ armedCount() }})\n </button>\n }\n <button (click)=\"openDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n <span aria-hidden=\"true\">\uFF0B</span>\n New mock\n </button>\n </div>\n </div>\n\n @if (draft(); as form) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>New mock</strong>\n <span class=\"et-query-devtools-count\">for any route, whether a query has ever called it or not</span>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select #clientSelect (change)=\"pickClient(clientSelect.value)\" class=\"et-query-devtools-select\">\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\" [selected]=\"name === form.clientName\">{{ name }}</option>\n }\n </select>\n <small>a mock answers one client, matched by name</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Method</span>\n <select #methodSelect (change)=\"patchDraft({ method: methodSelect.value })\" class=\"et-query-devtools-select\">\n @for (method of METHODS; track method) {\n <option [value]=\"method\" [selected]=\"method === form.method\">{{ method }}</option>\n }\n </select>\n <small>matched exactly</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Path</span>\n <input\n #patternInput\n [value]=\"form.pattern\"\n (input)=\"patchDraft({ pattern: patternInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"/posts/:id/comments\"\n />\n <small><code>:name</code> matches any one segment, so the route is mocked rather than one URL</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Query</span>\n <input\n #queryInput\n [value]=\"form.query\"\n (input)=\"patchDraft({ query: queryInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"page=2&limit=10\"\n />\n <small>\n parameters the request must carry; anything else it asks for is ignored. Empty answers whatever the query\n string is - and a mock naming parameters wins over one that does not\n </small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusDraftInput\n [value]=\"form.status\"\n (input)=\"patchDraft({ status: statusDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyDraftInput\n [value]=\"form.latencyMs\"\n (input)=\"patchDraft({ latencyMs: latencyDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles</small>\n </label>\n </div>\n\n @if (hasSchema()) {\n <div class=\"et-query-devtools-seed\">\n <span class=\"et-query-devtools-section\">Seed</span>\n @if (isSchemaReady()) {\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [options]=\"routeOptions()\"\n (pickOption)=\"pickRoute($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n pickOnly\n >\n <input etSelectSearch placeholder=\"search the routes it declares\u2026\" />\n </et-select>\n <button\n (click)=\"seedFromDraftRoute()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Generate a body from what the description says this path returns\"\n >\n From this path\n </button>\n </div>\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [value]=\"pickedSchema()\"\n [options]=\"schemaNameOptions()\"\n (valueChange)=\"pickSchema($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n >\n <input etSelectSearch placeholder=\"search its named types\u2026\" />\n </et-select>\n <button\n [disabled]=\"!pickedSchema()\"\n (click)=\"seedFromSchema()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n From this type\n </button>\n </div>\n <select\n #stylePick\n (change)=\"pickSeedStyle(stylePick.value)\"\n class=\"et-query-devtools-select\"\n title=\"How lifelike the generated values are\"\n >\n @for (style of SEED_STYLES; track style.value) {\n <option [value]=\"style.value\" [selected]=\"style.value === seedStyle()\">{{ style.label }}</option>\n }\n </select>\n <small class=\"et-query-devtools-hint et-query-devtools-seed-hint\">{{ seedStyleHint() }}</small>\n } @else if (schemaError(); as error) {\n <span class=\"et-query-devtools-settings-warn\">The API description could not be loaded: {{ error }}</span>\n } @else {\n <span class=\"et-query-devtools-count\">Loading the API description\u2026</span>\n }\n </div>\n }\n\n <label class=\"et-query-devtools-mock-body\">\n <span class=\"et-query-devtools-section\">Body</span>\n <textarea\n #bodyInput\n [value]=\"form.body\"\n (input)=\"patchDraft({ body: bodyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-mock-textarea\"\n rows=\"8\"\n spellcheck=\"false\"\n ></textarea>\n </label>\n\n @if (draftSeed(); as seed) {\n <p class=\"et-query-devtools-hint\">\n Generated from the API description{{ seed.schemaName ? ' as ' + seed.schemaName : '' }} - a starting point,\n not the contract. Saving opens the designer on it.\n </p>\n @if (seed.notes.length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of seed.notes; track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n }\n\n @if (draftError(); as error) {\n <p class=\"et-query-devtools-settings-warn\">{{ error }}</p>\n }\n\n <div class=\"et-query-devtools-mock-head\">\n <button (click)=\"saveDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n Save to the library\n </button>\n <button (click)=\"closeDraft()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Designed</strong>\n <span class=\"et-query-devtools-count\">{{ rows().length }} in the library</span>\n @if (rows().length) {\n <div class=\"et-query-devtools-spec-export\">\n <select #formatPick (change)=\"pickSpecFormat(formatPick.value)\" class=\"et-query-devtools-select\">\n @for (format of SPEC_FORMATS; track format.value) {\n <option [value]=\"format.value\" [selected]=\"format.value === specFormat()\">{{ format.label }}</option>\n }\n </select>\n <button\n (click)=\"exportLibrary()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every designed route as one OpenAPI 3.1 document - the response schemas inferred from the designed bodies, each body as an example\"\n >\n <span aria-hidden=\"true\">\u21E9</span>\n Export OpenAPI\n </button>\n </div>\n }\n </div>\n\n @for (row of rows(); track row.mock.id) {\n <div [class.et-query-devtools-mock-row--armed]=\"row.armed\" class=\"et-query-devtools-mock-row\">\n <div class=\"et-query-devtools-mock-head\">\n <button\n [class.et-query-devtools-btn--tampered]=\"row.armed\"\n [attr.aria-pressed]=\"row.armed\"\n (click)=\"toggleArmed(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"{{ row.armed ? 'Stop serving this response' : 'Serve this response instead of the request' }}\"\n >\n {{ row.armed ? 'Armed' : 'Arm' }}\n </button>\n <span class=\"et-query-devtools-method\">{{ row.mock.method }}</span>\n <span class=\"et-query-devtools-route et-query-devtools-mono\">{{ row.mock.pattern }}</span>\n @if (row.mock.query) {\n <span class=\"et-query-devtools-url\">?{{ row.mock.query }}</span>\n }\n <span class=\"et-query-devtools-url\">{{ row.mock.clientName }}</span>\n @if (row.mock.capturedAt) {\n <span class=\"et-query-devtools-chip\">captured</span>\n }\n @if (row.mock.schemaName; as schemaName) {\n <span class=\"et-query-devtools-chip\" title=\"Seeded from the API description\">{{ schemaName }}</span>\n }\n @if (row.isSecure) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"A mock sends nothing, so no token is attached and the auth flow is never exercised\"\n >\n no auth\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"editBody(row)\" class=\"et-query-devtools-btn\" type=\"button\">Body</button>\n <button\n (click)=\"copyDefinition(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as a @ethlete/query definition - the response type inferred from the body, the args contract and the creator call\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedId() === row.mock.id ? 'Copied' : 'TS' }}\n </button>\n <button\n (click)=\"copyPathItem(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as an OpenAPI paths entry, ready to paste under an existing description\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedSpecId() === row.mock.id ? 'Copied' : 'OAS' }}\n </button>\n <button (click)=\"remove(row)\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Delete\n </button>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusInput\n [value]=\"row.mock.status\"\n (change)=\"setStatus(row, statusInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error, body and all</small>\n </label>\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"row.mock.latencyMs\"\n (change)=\"setLatency(row, latencyInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles, so the route still has a loading state</small>\n </label>\n </div>\n\n @if (editingId() === row.mock.id) {\n <et-query-devtools-mock-designer\n [body]=\"row.mock.body\"\n [annotations]=\"editingAnnotations()\"\n (save)=\"saveBody(row, $event)\"\n (dismiss)=\"cancelBody()\"\n />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">\n Nothing designed yet. <strong>New mock</strong> writes one from scratch; capturing below seeds one from\n something that actually happened.\n </p>\n }\n\n @if (specNotes().length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of specNotes(); track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Capture</strong>\n <span class=\"et-query-devtools-count\">live queries holding a response</span>\n </div>\n\n @for (row of captureRows(); track row.entry.id) {\n <div class=\"et-query-devtools-mock-head\">\n <span class=\"et-query-devtools-method\">{{ row.method }}</span>\n <button\n (click)=\"host.selectQuery(row.entry.id)\"\n class=\"et-query-devtools-route et-query-devtools-mono et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open this query\"\n >\n {{ row.pattern }}\n </button>\n <span class=\"et-query-devtools-url\">{{ row.clientName }}</span>\n @if (row.shared > 1) {\n <span class=\"et-query-devtools-count\" title=\"A mock answers the route, so it answers all of them\">\n {{ row.shared }} queries\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"capture(row)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ row.exists ? 'Re-capture' : 'Capture' }}\n </button>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No live query has a response yet.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-mocks-toolbar{align-items:flex-start}.et-query-devtools-mock-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-mock-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-mock-row--armed{background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-mock-head{display:flex;align-items:center;flex-wrap:wrap;gap:.6rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-mock-fields,.et-query-devtools-seed,.et-query-devtools-mock-body{max-inline-size:96rem}.et-query-devtools-mock-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(38rem,1fr));align-items:start;gap:.4rem 1.6rem;padding-block-start:.4rem}.et-query-devtools-mock-fields .et-query-devtools-field{display:grid;grid-template-columns:7rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-mock-fields .et-query-devtools-select{justify-self:start;max-inline-size:100%}.et-query-devtools-mock-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-mock-fields .et-query-devtools-field--wide{grid-column:1 / -1;grid-template-columns:7rem minmax(0,1fr)}.et-query-devtools-spec-export{display:flex;align-items:center;gap:.6rem;margin-inline-start:auto}.et-query-devtools-seed{display:flex;align-items:center;flex-wrap:wrap;gap:.4rem 2.4rem;padding-block-start:.4rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-seed-pick{display:flex;align-items:center;gap:.2rem}.et-query-devtools-seed .et-query-devtools-select{max-inline-size:32rem}.et-query-devtools-combobox{flex:0 1 auto;inline-size:32rem;min-block-size:var(--_et-qdt-control-h);box-sizing:border-box;background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .6rem;font-size:1.15rem}.et-query-devtools-combobox input{background:none;border:0;padding:0;color:inherit;font:inherit}.et-query-devtools-seed-hint{flex-basis:100%;margin:0}.et-query-devtools-mock-body{display:block;padding-block-start:.4rem}.et-query-devtools-mock-textarea{display:block;inline-size:100%;resize:vertical;font-family:inherit;font-size:1.15rem;line-height:1.5;tab-size:2}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsMockDesignerComponent, selector: "et-query-devtools-mock-designer", inputs: ["body", "annotations", "notes"], outputs: ["save", "dismiss"] }, { kind: "component", type: SelectComponent, selector: "et-select", inputs: ["loadMoreLabel", "addNewLabel", "createLabel", "clearable", "clearLabel", "selectAllLabel"] }, { kind: "directive", type: SelectSearchDirective, selector: "input[etSelectSearch]", inputs: ["query"], outputs: ["queryChange"], exportAs: ["etSelectSearch"] }], encapsulation: i0.ViewEncapsulation.None }); }
3294
3558
  }
3295
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsMocksTabComponent, decorators: [{
3559
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsMocksTabComponent, decorators: [{
3296
3560
  type: Component,
3297
3561
  args: [{ selector: 'et-query-devtools-mocks-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsMockDesignerComponent, SelectComponent, SelectSearchDirective], template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-mocks-toolbar\">\n <p class=\"et-query-devtools-hint\">\n A mock replaces the request, so it answers a route that has never run - or does not exist yet. It bypasses the\n interceptor chain by construction, so a mocked secure route never exercises the token flow. The library is kept;\n whether a mock is <strong>armed</strong> is not, so a reload always stops serving them. GraphQL queries are not\n offered: they all POST one route, so matching them needs the document rather than the path.\n </p>\n <div class=\"et-query-devtools-settings-controls\">\n @if (rows().length && armedCount() < rows().length) {\n <button\n (click)=\"ARM_ALL()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Serve every mock in the library - the whole screen off the network at once\"\n >\n Arm all ({{ rows().length }})\n </button>\n }\n @if (armedCount()) {\n <button (click)=\"DISARM_ALL()\" class=\"et-query-devtools-btn\" type=\"button\">\n Disarm all ({{ armedCount() }})\n </button>\n }\n <button (click)=\"openDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n <span aria-hidden=\"true\">\uFF0B</span>\n New mock\n </button>\n </div>\n </div>\n\n @if (draft(); as form) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>New mock</strong>\n <span class=\"et-query-devtools-count\">for any route, whether a query has ever called it or not</span>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select #clientSelect (change)=\"pickClient(clientSelect.value)\" class=\"et-query-devtools-select\">\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\" [selected]=\"name === form.clientName\">{{ name }}</option>\n }\n </select>\n <small>a mock answers one client, matched by name</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Method</span>\n <select #methodSelect (change)=\"patchDraft({ method: methodSelect.value })\" class=\"et-query-devtools-select\">\n @for (method of METHODS; track method) {\n <option [value]=\"method\" [selected]=\"method === form.method\">{{ method }}</option>\n }\n </select>\n <small>matched exactly</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Path</span>\n <input\n #patternInput\n [value]=\"form.pattern\"\n (input)=\"patchDraft({ pattern: patternInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"/posts/:id/comments\"\n />\n <small><code>:name</code> matches any one segment, so the route is mocked rather than one URL</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Query</span>\n <input\n #queryInput\n [value]=\"form.query\"\n (input)=\"patchDraft({ query: queryInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"page=2&limit=10\"\n />\n <small>\n parameters the request must carry; anything else it asks for is ignored. Empty answers whatever the query\n string is - and a mock naming parameters wins over one that does not\n </small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusDraftInput\n [value]=\"form.status\"\n (input)=\"patchDraft({ status: statusDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyDraftInput\n [value]=\"form.latencyMs\"\n (input)=\"patchDraft({ latencyMs: latencyDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles</small>\n </label>\n </div>\n\n @if (hasSchema()) {\n <div class=\"et-query-devtools-seed\">\n <span class=\"et-query-devtools-section\">Seed</span>\n @if (isSchemaReady()) {\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [options]=\"routeOptions()\"\n (pickOption)=\"pickRoute($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n pickOnly\n >\n <input etSelectSearch placeholder=\"search the routes it declares\u2026\" />\n </et-select>\n <button\n (click)=\"seedFromDraftRoute()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Generate a body from what the description says this path returns\"\n >\n From this path\n </button>\n </div>\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [value]=\"pickedSchema()\"\n [options]=\"schemaNameOptions()\"\n (valueChange)=\"pickSchema($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n >\n <input etSelectSearch placeholder=\"search its named types\u2026\" />\n </et-select>\n <button\n [disabled]=\"!pickedSchema()\"\n (click)=\"seedFromSchema()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n From this type\n </button>\n </div>\n <select\n #stylePick\n (change)=\"pickSeedStyle(stylePick.value)\"\n class=\"et-query-devtools-select\"\n title=\"How lifelike the generated values are\"\n >\n @for (style of SEED_STYLES; track style.value) {\n <option [value]=\"style.value\" [selected]=\"style.value === seedStyle()\">{{ style.label }}</option>\n }\n </select>\n <small class=\"et-query-devtools-hint et-query-devtools-seed-hint\">{{ seedStyleHint() }}</small>\n } @else if (schemaError(); as error) {\n <span class=\"et-query-devtools-settings-warn\">The API description could not be loaded: {{ error }}</span>\n } @else {\n <span class=\"et-query-devtools-count\">Loading the API description\u2026</span>\n }\n </div>\n }\n\n <label class=\"et-query-devtools-mock-body\">\n <span class=\"et-query-devtools-section\">Body</span>\n <textarea\n #bodyInput\n [value]=\"form.body\"\n (input)=\"patchDraft({ body: bodyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-mock-textarea\"\n rows=\"8\"\n spellcheck=\"false\"\n ></textarea>\n </label>\n\n @if (draftSeed(); as seed) {\n <p class=\"et-query-devtools-hint\">\n Generated from the API description{{ seed.schemaName ? ' as ' + seed.schemaName : '' }} - a starting point,\n not the contract. Saving opens the designer on it.\n </p>\n @if (seed.notes.length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of seed.notes; track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n }\n\n @if (draftError(); as error) {\n <p class=\"et-query-devtools-settings-warn\">{{ error }}</p>\n }\n\n <div class=\"et-query-devtools-mock-head\">\n <button (click)=\"saveDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n Save to the library\n </button>\n <button (click)=\"closeDraft()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Designed</strong>\n <span class=\"et-query-devtools-count\">{{ rows().length }} in the library</span>\n @if (rows().length) {\n <div class=\"et-query-devtools-spec-export\">\n <select #formatPick (change)=\"pickSpecFormat(formatPick.value)\" class=\"et-query-devtools-select\">\n @for (format of SPEC_FORMATS; track format.value) {\n <option [value]=\"format.value\" [selected]=\"format.value === specFormat()\">{{ format.label }}</option>\n }\n </select>\n <button\n (click)=\"exportLibrary()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every designed route as one OpenAPI 3.1 document - the response schemas inferred from the designed bodies, each body as an example\"\n >\n <span aria-hidden=\"true\">\u21E9</span>\n Export OpenAPI\n </button>\n </div>\n }\n </div>\n\n @for (row of rows(); track row.mock.id) {\n <div [class.et-query-devtools-mock-row--armed]=\"row.armed\" class=\"et-query-devtools-mock-row\">\n <div class=\"et-query-devtools-mock-head\">\n <button\n [class.et-query-devtools-btn--tampered]=\"row.armed\"\n [attr.aria-pressed]=\"row.armed\"\n (click)=\"toggleArmed(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"{{ row.armed ? 'Stop serving this response' : 'Serve this response instead of the request' }}\"\n >\n {{ row.armed ? 'Armed' : 'Arm' }}\n </button>\n <span class=\"et-query-devtools-method\">{{ row.mock.method }}</span>\n <span class=\"et-query-devtools-route et-query-devtools-mono\">{{ row.mock.pattern }}</span>\n @if (row.mock.query) {\n <span class=\"et-query-devtools-url\">?{{ row.mock.query }}</span>\n }\n <span class=\"et-query-devtools-url\">{{ row.mock.clientName }}</span>\n @if (row.mock.capturedAt) {\n <span class=\"et-query-devtools-chip\">captured</span>\n }\n @if (row.mock.schemaName; as schemaName) {\n <span class=\"et-query-devtools-chip\" title=\"Seeded from the API description\">{{ schemaName }}</span>\n }\n @if (row.isSecure) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"A mock sends nothing, so no token is attached and the auth flow is never exercised\"\n >\n no auth\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"editBody(row)\" class=\"et-query-devtools-btn\" type=\"button\">Body</button>\n <button\n (click)=\"copyDefinition(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as a @ethlete/query definition - the response type inferred from the body, the args contract and the creator call\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedId() === row.mock.id ? 'Copied' : 'TS' }}\n </button>\n <button\n (click)=\"copyPathItem(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as an OpenAPI paths entry, ready to paste under an existing description\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedSpecId() === row.mock.id ? 'Copied' : 'OAS' }}\n </button>\n <button (click)=\"remove(row)\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Delete\n </button>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusInput\n [value]=\"row.mock.status\"\n (change)=\"setStatus(row, statusInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error, body and all</small>\n </label>\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"row.mock.latencyMs\"\n (change)=\"setLatency(row, latencyInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles, so the route still has a loading state</small>\n </label>\n </div>\n\n @if (editingId() === row.mock.id) {\n <et-query-devtools-mock-designer\n [body]=\"row.mock.body\"\n [annotations]=\"editingAnnotations()\"\n (save)=\"saveBody(row, $event)\"\n (dismiss)=\"cancelBody()\"\n />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">\n Nothing designed yet. <strong>New mock</strong> writes one from scratch; capturing below seeds one from\n something that actually happened.\n </p>\n }\n\n @if (specNotes().length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of specNotes(); track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Capture</strong>\n <span class=\"et-query-devtools-count\">live queries holding a response</span>\n </div>\n\n @for (row of captureRows(); track row.entry.id) {\n <div class=\"et-query-devtools-mock-head\">\n <span class=\"et-query-devtools-method\">{{ row.method }}</span>\n <button\n (click)=\"host.selectQuery(row.entry.id)\"\n class=\"et-query-devtools-route et-query-devtools-mono et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open this query\"\n >\n {{ row.pattern }}\n </button>\n <span class=\"et-query-devtools-url\">{{ row.clientName }}</span>\n @if (row.shared > 1) {\n <span class=\"et-query-devtools-count\" title=\"A mock answers the route, so it answers all of them\">\n {{ row.shared }} queries\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"capture(row)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ row.exists ? 'Re-capture' : 'Capture' }}\n </button>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No live query has a response yet.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-mocks-toolbar{align-items:flex-start}.et-query-devtools-mock-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-mock-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-mock-row--armed{background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-mock-head{display:flex;align-items:center;flex-wrap:wrap;gap:.6rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-mock-fields,.et-query-devtools-seed,.et-query-devtools-mock-body{max-inline-size:96rem}.et-query-devtools-mock-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(38rem,1fr));align-items:start;gap:.4rem 1.6rem;padding-block-start:.4rem}.et-query-devtools-mock-fields .et-query-devtools-field{display:grid;grid-template-columns:7rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-mock-fields .et-query-devtools-select{justify-self:start;max-inline-size:100%}.et-query-devtools-mock-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-mock-fields .et-query-devtools-field--wide{grid-column:1 / -1;grid-template-columns:7rem minmax(0,1fr)}.et-query-devtools-spec-export{display:flex;align-items:center;gap:.6rem;margin-inline-start:auto}.et-query-devtools-seed{display:flex;align-items:center;flex-wrap:wrap;gap:.4rem 2.4rem;padding-block-start:.4rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-seed-pick{display:flex;align-items:center;gap:.2rem}.et-query-devtools-seed .et-query-devtools-select{max-inline-size:32rem}.et-query-devtools-combobox{flex:0 1 auto;inline-size:32rem;min-block-size:var(--_et-qdt-control-h);box-sizing:border-box;background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .6rem;font-size:1.15rem}.et-query-devtools-combobox input{background:none;border:0;padding:0;color:inherit;font:inherit}.et-query-devtools-seed-hint{flex-basis:100%;margin:0}.et-query-devtools-mock-body{display:block;padding-block-start:.4rem}.et-query-devtools-mock-textarea{display:block;inline-size:100%;resize:vertical;font-family:inherit;font-size:1.15rem;line-height:1.5;tab-size:2}}\n"] }]
3298
3562
  }], ctorParameters: () => [] });
@@ -3326,10 +3590,10 @@ class QueryDevtoolsFaultsTabComponent {
3326
3590
  isFaultStatusRetryable(status) {
3327
3591
  return QUERY_DEVTOOLS_FAULT_STATUSES.find((entry) => entry.status === status)?.retryable ?? false;
3328
3592
  }
3329
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsFaultsTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3330
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsFaultsTabComponent, isStandalone: true, selector: "et-query-devtools-faults-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Faults are injected into the request pipeline, so retries, error handling features and the cache see them exactly\n as they would see a real one. Unlike the rest of the panel, what you arm here is not persisted - a page reload\n disarms every client.\n </p>\n </div>\n\n @for (client of host.faultClients(); track client.name) {\n @let fault = client.fault;\n <div [class.et-query-devtools-card--armed]=\"client.armed\" class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.name }}</strong>\n <span class=\"et-query-devtools-url\">{{ client.baseUrl }}</span>\n @if (client.armed) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">armed</span>\n <button (click)=\"CLEAR_FAULTS(client.name)\" class=\"et-query-devtools-btn\" type=\"button\">Disarm</button>\n }\n </div>\n\n <div class=\"et-query-devtools-faults\">\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"fault.latencyMs\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'latencyMs', value: latencyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before every attempt starts</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail next</span>\n <input\n #failNextInput\n [value]=\"fault.failNext\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failNext', value: failNextInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"99\"\n step=\"1\"\n />\n <small>attempts, counted down as they are spent</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail rate</span>\n <input\n #failRateInput\n [value]=\"fault.failRate\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failRate', value: failRateInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n />\n <small>% of attempts, rolled per attempt</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <select\n #statusSelect\n (change)=\"armFaultStatus({ clientName: client.name, value: statusSelect.value })\"\n class=\"et-query-devtools-select\"\n >\n @for (option of FAULT_STATUSES; track option.status) {\n <!-- `selected` on the option, not `value` on the select: the select's value is\n applied before its options exist, so it would fall back to the first one. -->\n <option [value]=\"option.status\" [selected]=\"option.status === fault.status\">\n {{ option.status }} \u00B7 {{ option.label }}\n </option>\n }\n </select>\n <small>\n @if (isFaultStatusRetryable(fault.status)) {\n the default retry policy retries this\n } @else {\n the default retry policy gives up on this\n }\n </small>\n </label>\n </div>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No query clients registered yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-faults{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));gap:.8rem 1.6rem}.et-query-devtools-faults .et-query-devtools-field{display:grid;grid-template-columns:8rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-faults .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
3593
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsFaultsTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3594
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsFaultsTabComponent, isStandalone: true, selector: "et-query-devtools-faults-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Faults are injected into the request pipeline, so retries, error handling features and the cache see them exactly\n as they would see a real one. Unlike the rest of the panel, what you arm here is not persisted - a page reload\n disarms every client.\n </p>\n </div>\n\n @for (client of host.faultClients(); track client.name) {\n @let fault = client.fault;\n <div [class.et-query-devtools-card--armed]=\"client.armed\" class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.name }}</strong>\n <span class=\"et-query-devtools-url\">{{ client.baseUrl }}</span>\n @if (client.armed) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">armed</span>\n <button (click)=\"CLEAR_FAULTS(client.name)\" class=\"et-query-devtools-btn\" type=\"button\">Disarm</button>\n }\n </div>\n\n <div class=\"et-query-devtools-faults\">\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"fault.latencyMs\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'latencyMs', value: latencyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before every attempt starts</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail next</span>\n <input\n #failNextInput\n [value]=\"fault.failNext\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failNext', value: failNextInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"99\"\n step=\"1\"\n />\n <small>attempts, counted down as they are spent</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail rate</span>\n <input\n #failRateInput\n [value]=\"fault.failRate\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failRate', value: failRateInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n />\n <small>% of attempts, rolled per attempt</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <select\n #statusSelect\n (change)=\"armFaultStatus({ clientName: client.name, value: statusSelect.value })\"\n class=\"et-query-devtools-select\"\n >\n @for (option of FAULT_STATUSES; track option.status) {\n <!-- `selected` on the option, not `value` on the select: the select's value is\n applied before its options exist, so it would fall back to the first one. -->\n <option [value]=\"option.status\" [selected]=\"option.status === fault.status\">\n {{ option.status }} \u00B7 {{ option.label }}\n </option>\n }\n </select>\n <small>\n @if (isFaultStatusRetryable(fault.status)) {\n the default retry policy retries this\n } @else {\n the default retry policy gives up on this\n }\n </small>\n </label>\n </div>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No query clients registered yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-faults{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));gap:.8rem 1.6rem}.et-query-devtools-faults .et-query-devtools-field{display:grid;grid-template-columns:8rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-faults .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
3331
3595
  }
3332
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsFaultsTabComponent, decorators: [{
3596
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsFaultsTabComponent, decorators: [{
3333
3597
  type: Component,
3334
3598
  args: [{ selector: 'et-query-devtools-faults-tab', encapsulation: ViewEncapsulation.None, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Faults are injected into the request pipeline, so retries, error handling features and the cache see them exactly\n as they would see a real one. Unlike the rest of the panel, what you arm here is not persisted - a page reload\n disarms every client.\n </p>\n </div>\n\n @for (client of host.faultClients(); track client.name) {\n @let fault = client.fault;\n <div [class.et-query-devtools-card--armed]=\"client.armed\" class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.name }}</strong>\n <span class=\"et-query-devtools-url\">{{ client.baseUrl }}</span>\n @if (client.armed) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">armed</span>\n <button (click)=\"CLEAR_FAULTS(client.name)\" class=\"et-query-devtools-btn\" type=\"button\">Disarm</button>\n }\n </div>\n\n <div class=\"et-query-devtools-faults\">\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"fault.latencyMs\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'latencyMs', value: latencyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before every attempt starts</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail next</span>\n <input\n #failNextInput\n [value]=\"fault.failNext\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failNext', value: failNextInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"99\"\n step=\"1\"\n />\n <small>attempts, counted down as they are spent</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail rate</span>\n <input\n #failRateInput\n [value]=\"fault.failRate\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failRate', value: failRateInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n />\n <small>% of attempts, rolled per attempt</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <select\n #statusSelect\n (change)=\"armFaultStatus({ clientName: client.name, value: statusSelect.value })\"\n class=\"et-query-devtools-select\"\n >\n @for (option of FAULT_STATUSES; track option.status) {\n <!-- `selected` on the option, not `value` on the select: the select's value is\n applied before its options exist, so it would fall back to the first one. -->\n <option [value]=\"option.status\" [selected]=\"option.status === fault.status\">\n {{ option.status }} \u00B7 {{ option.label }}\n </option>\n }\n </select>\n <small>\n @if (isFaultStatusRetryable(fault.status)) {\n the default retry policy retries this\n } @else {\n the default retry policy gives up on this\n }\n </small>\n </label>\n </div>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No query clients registered yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-faults{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));gap:.8rem 1.6rem}.et-query-devtools-faults .et-query-devtools-field{display:grid;grid-template-columns:8rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-faults .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"] }]
3335
3599
  }] });
@@ -3339,10 +3603,10 @@ class QueryDevtoolsFormsTabComponent {
3339
3603
  constructor() {
3340
3604
  this.host = injectQueryDevtoolsHost();
3341
3605
  }
3342
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsFormsTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3343
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsFormsTabComponent, isStandalone: true, selector: "et-query-devtools-forms-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.formEntries(); track entry.id) {\n @let form = host.asForm(entry);\n @let expanded = host.selectedFormId() === entry.id;\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <button\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.selectedFormId.set(expanded ? null : entry.id)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-caret\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n <strong>{{ entry.meta.name || 'query form' }}</strong>\n </button>\n @if (form.activeFilterCount(); as count) {\n <span class=\"et-query-devtools-chip\">{{ count }} active</span>\n } @else {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">at defaults</span>\n }\n @if (form.isCommitPending()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">debouncing</span>\n }\n @if (!form.isObserving()) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"observe() has not been called, so nothing syncs with the URL\"\n >\n not observing\n </span>\n }\n <button\n [disabled]=\"form.isAtDefaults()\"\n (click)=\"form.resetAll()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n \u27F2 Reset all\n </button>\n </div>\n\n @let driven = host.queriesDrivenByForm(entry);\n <dl class=\"et-query-devtools-meta\">\n <dt>Drives</dt>\n <dd>\n @if (driven.length) {\n <div class=\"et-query-devtools-inner-queries\">\n @for (link of driven; track link.id) {\n <button\n [class.et-query-devtools-inner-row--active]=\"host.formSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"host.formSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span [attr.data-status]=\"host.queryStatus(link.query)\" class=\"et-query-devtools-status\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" [fallback]=\"link.query.id() || 'query'\" />\n </span>\n </button>\n }\n </div>\n } @else {\n <span class=\"et-query-devtools-hint\">\n No query has read this form's value yet. A query picks it up the first time its\n <code>withArgs</code> reads <code>value()</code>.\n </span>\n }\n </dd>\n </dl>\n\n @if (expanded) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Field</th>\n <th>Value</th>\n <th>Default</th>\n <th>URL param</th>\n <th>Rules</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (field of form.fields(); track field.key) {\n <tr [attr.data-status]=\"field.isDefault ? null : 'success'\">\n <td class=\"et-query-devtools-mono\">{{ field.key }}</td>\n <td class=\"et-query-devtools-mono\">\n {{ host.inlineValue(field.value) }}\n <!-- The live value only differs while a debounce is pending, which is exactly\n when \"the input says X but the query asked for Y\" needs explaining. -->\n @if (field.liveValue !== field.value) {\n <span class=\"et-query-devtools-stat-sub\">typing {{ host.inlineValue(field.liveValue) }}</span>\n }\n </td>\n <td class=\"et-query-devtools-mono\">{{ host.inlineValue(field.defaultValue) }}</td>\n <td class=\"et-query-devtools-mono\">\n @if (field.queryParam === undefined) {\n <span class=\"et-query-devtools-hint\">\u2014</span>\n } @else {\n {{ field.paramKey }}={{ host.inlineValue(field.queryParam) }}\n }\n </td>\n <td>\n <div class=\"et-query-devtools-chips\">\n @if (field.debounceMs !== null) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">\n {{ field.debounceMs }}ms\n </span>\n }\n @if (field.isResetBy.length) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Reset to its default whenever one of these changes\"\n >\n reset by {{ field.isResetBy.join(', ') }}\n </span>\n }\n @if (!field.countsAsFilter) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Not counted by activeFilterCount\"\n >\n not a filter\n </span>\n }\n </div>\n </td>\n <td>\n <button\n [disabled]=\"field.isDefault\"\n (click)=\"form.resetField(field.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n \u27F2\n </button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n\n <h4 class=\"et-query-devtools-section\">Committed value</h4>\n <et-query-devtools-json [value]=\"form.value()\" />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">\n No query forms registered. A form created with <code>defineQueryForm()</code> registers itself.\n </p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.formSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.formSelectedQueryId.set(null)\"\n />\n</div>\n", styles: ["@layer components{.et-query-devtools-caret{display:inline-block;width:1.2rem;color:var(--_et-qdt-muted)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
3606
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsFormsTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3607
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsFormsTabComponent, isStandalone: true, selector: "et-query-devtools-forms-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.formEntries(); track entry.id) {\n @let form = host.asForm(entry);\n @let expanded = host.selectedFormId() === entry.id;\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <button\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.selectedFormId.set(expanded ? null : entry.id)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-caret\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n <strong>{{ entry.meta.name || 'query form' }}</strong>\n </button>\n @if (form.activeFilterCount(); as count) {\n <span class=\"et-query-devtools-chip\">{{ count }} active</span>\n } @else {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">at defaults</span>\n }\n @if (form.isCommitPending()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">debouncing</span>\n }\n @if (!form.isObserving()) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"observe() has not been called, so nothing syncs with the URL\"\n >\n not observing\n </span>\n }\n <button\n [disabled]=\"form.isAtDefaults()\"\n (click)=\"form.resetAll()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n \u27F2 Reset all\n </button>\n </div>\n\n @let driven = host.queriesDrivenByForm(entry);\n <dl class=\"et-query-devtools-meta\">\n <dt>Drives</dt>\n <dd>\n @if (driven.length) {\n <div class=\"et-query-devtools-inner-queries\">\n @for (link of driven; track link.id) {\n <button\n [class.et-query-devtools-inner-row--active]=\"host.formSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"host.formSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span [attr.data-status]=\"host.queryStatus(link.query)\" class=\"et-query-devtools-status\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" [fallback]=\"link.query.id() || 'query'\" />\n </span>\n </button>\n }\n </div>\n } @else {\n <span class=\"et-query-devtools-hint\">\n No query has read this form's value yet. A query picks it up the first time its\n <code>withArgs</code> reads <code>value()</code>.\n </span>\n }\n </dd>\n </dl>\n\n @if (expanded) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Field</th>\n <th>Value</th>\n <th>Default</th>\n <th>URL param</th>\n <th>Rules</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (field of form.fields(); track field.key) {\n <tr [attr.data-status]=\"field.isDefault ? null : 'success'\">\n <td class=\"et-query-devtools-mono\">{{ field.key }}</td>\n <td class=\"et-query-devtools-mono\">\n {{ host.inlineValue(field.value) }}\n <!-- The live value only differs while a debounce is pending, which is exactly\n when \"the input says X but the query asked for Y\" needs explaining. -->\n @if (field.liveValue !== field.value) {\n <span class=\"et-query-devtools-stat-sub\">typing {{ host.inlineValue(field.liveValue) }}</span>\n }\n </td>\n <td class=\"et-query-devtools-mono\">{{ host.inlineValue(field.defaultValue) }}</td>\n <td class=\"et-query-devtools-mono\">\n @if (field.queryParam === undefined) {\n <span class=\"et-query-devtools-hint\">\u2014</span>\n } @else {\n {{ field.paramKey }}={{ host.inlineValue(field.queryParam) }}\n }\n </td>\n <td>\n <div class=\"et-query-devtools-chips\">\n @if (field.debounceMs !== null) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">\n {{ field.debounceMs }}ms\n </span>\n }\n @if (field.isResetBy.length) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Reset to its default whenever one of these changes\"\n >\n reset by {{ field.isResetBy.join(', ') }}\n </span>\n }\n @if (!field.countsAsFilter) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Not counted by activeFilterCount\"\n >\n not a filter\n </span>\n }\n </div>\n </td>\n <td>\n <button\n [disabled]=\"field.isDefault\"\n (click)=\"form.resetField(field.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n \u27F2\n </button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n\n <h4 class=\"et-query-devtools-section\">Committed value</h4>\n <et-query-devtools-json [value]=\"form.value()\" />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">\n No query forms registered. A form created with <code>defineQueryForm()</code> registers itself.\n </p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.formSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.formSelectedQueryId.set(null)\"\n />\n</div>\n", styles: ["@layer components{.et-query-devtools-caret{display:inline-block;width:1.2rem;color:var(--_et-qdt-muted)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
3344
3608
  }
3345
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsFormsTabComponent, decorators: [{
3609
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsFormsTabComponent, decorators: [{
3346
3610
  type: Component,
3347
3611
  args: [{ selector: 'et-query-devtools-forms-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDrawerComponent, QueryDevtoolsJsonComponent, QueryDevtoolsRouteComponent], template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.formEntries(); track entry.id) {\n @let form = host.asForm(entry);\n @let expanded = host.selectedFormId() === entry.id;\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <button\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.selectedFormId.set(expanded ? null : entry.id)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-caret\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n <strong>{{ entry.meta.name || 'query form' }}</strong>\n </button>\n @if (form.activeFilterCount(); as count) {\n <span class=\"et-query-devtools-chip\">{{ count }} active</span>\n } @else {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">at defaults</span>\n }\n @if (form.isCommitPending()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">debouncing</span>\n }\n @if (!form.isObserving()) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"observe() has not been called, so nothing syncs with the URL\"\n >\n not observing\n </span>\n }\n <button\n [disabled]=\"form.isAtDefaults()\"\n (click)=\"form.resetAll()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n \u27F2 Reset all\n </button>\n </div>\n\n @let driven = host.queriesDrivenByForm(entry);\n <dl class=\"et-query-devtools-meta\">\n <dt>Drives</dt>\n <dd>\n @if (driven.length) {\n <div class=\"et-query-devtools-inner-queries\">\n @for (link of driven; track link.id) {\n <button\n [class.et-query-devtools-inner-row--active]=\"host.formSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"host.formSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span [attr.data-status]=\"host.queryStatus(link.query)\" class=\"et-query-devtools-status\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" [fallback]=\"link.query.id() || 'query'\" />\n </span>\n </button>\n }\n </div>\n } @else {\n <span class=\"et-query-devtools-hint\">\n No query has read this form's value yet. A query picks it up the first time its\n <code>withArgs</code> reads <code>value()</code>.\n </span>\n }\n </dd>\n </dl>\n\n @if (expanded) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Field</th>\n <th>Value</th>\n <th>Default</th>\n <th>URL param</th>\n <th>Rules</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (field of form.fields(); track field.key) {\n <tr [attr.data-status]=\"field.isDefault ? null : 'success'\">\n <td class=\"et-query-devtools-mono\">{{ field.key }}</td>\n <td class=\"et-query-devtools-mono\">\n {{ host.inlineValue(field.value) }}\n <!-- The live value only differs while a debounce is pending, which is exactly\n when \"the input says X but the query asked for Y\" needs explaining. -->\n @if (field.liveValue !== field.value) {\n <span class=\"et-query-devtools-stat-sub\">typing {{ host.inlineValue(field.liveValue) }}</span>\n }\n </td>\n <td class=\"et-query-devtools-mono\">{{ host.inlineValue(field.defaultValue) }}</td>\n <td class=\"et-query-devtools-mono\">\n @if (field.queryParam === undefined) {\n <span class=\"et-query-devtools-hint\">\u2014</span>\n } @else {\n {{ field.paramKey }}={{ host.inlineValue(field.queryParam) }}\n }\n </td>\n <td>\n <div class=\"et-query-devtools-chips\">\n @if (field.debounceMs !== null) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">\n {{ field.debounceMs }}ms\n </span>\n }\n @if (field.isResetBy.length) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Reset to its default whenever one of these changes\"\n >\n reset by {{ field.isResetBy.join(', ') }}\n </span>\n }\n @if (!field.countsAsFilter) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Not counted by activeFilterCount\"\n >\n not a filter\n </span>\n }\n </div>\n </td>\n <td>\n <button\n [disabled]=\"field.isDefault\"\n (click)=\"form.resetField(field.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n \u27F2\n </button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n\n <h4 class=\"et-query-devtools-section\">Committed value</h4>\n <et-query-devtools-json [value]=\"form.value()\" />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">\n No query forms registered. A form created with <code>defineQueryForm()</code> registers itself.\n </p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.formSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.formSelectedQueryId.set(null)\"\n />\n</div>\n", styles: ["@layer components{.et-query-devtools-caret{display:inline-block;width:1.2rem;color:var(--_et-qdt-muted)}}\n"] }]
3348
3612
  }] });
@@ -3363,10 +3627,10 @@ class QueryDevtoolsLocksTabComponent {
3363
3627
  kindLabel(kind) {
3364
3628
  return KIND_LABELS[kind];
3365
3629
  }
3366
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsLocksTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3367
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsLocksTabComponent, isStandalone: true, selector: "et-query-devtools-locks-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <p class=\"et-query-devtools-hint et-query-devtools-locks-hint\">\n The\n <a href=\"https://developer.mozilla.org/docs/Web/API/Web_Locks_API\" target=\"_blank\" rel=\"noreferrer\">Web Locks</a>\n held across this whole origin - every other tab, worker and service worker included, which is the one thing in\n this panel that is not about this tab alone. Read only: a tab cannot release another tab's lock. Polled once a\n second while this tab is open and the page is visible.\n </p>\n </div>\n\n @if (host.lockRows(); as rows) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Lock</th>\n <th>Kind</th>\n <th>Taking part</th>\n <th>This tab</th>\n </tr>\n </thead>\n <tbody>\n @for (row of rows; track row.name) {\n <tr>\n <td [title]=\"row.name\">\n <span class=\"et-query-devtools-lock-name\">\n <span class=\"et-query-devtools-url\">{{ row.label }}</span>\n @if (row.channel) {\n <span class=\"et-query-devtools-count\">{{ row.channel }}</span>\n }\n </span>\n </td>\n <td>{{ kindLabel(row.kind) }}</td>\n <td>{{ row.tabs }}</td>\n <td>\n @let standing = host.lockStanding(row);\n <span\n [class.et-query-devtools-chip--success]=\"standing.tone === 'success'\"\n [class.et-query-devtools-chip--muted]=\"standing.tone === 'muted'\"\n [title]=\"standing.title\"\n class=\"et-query-devtools-chip\"\n >\n {{ standing.label }}\n </span>\n </td>\n </tr>\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"4\">\n Nothing holds a lock on this origin. Multi-tab auth sync and cache polling both take one - without either,\n there is nothing to coordinate.\n </td>\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <p class=\"et-query-devtools-empty\">\n This browser has no Web Locks API, so nothing is coordinated across tabs at all: every tab refreshes its own\n tokens and polls its own cache keys.\n </p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-locks-hint{max-inline-size:80ch}.et-query-devtools-lock-name{display:flex;align-items:center;gap:.8rem}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
3630
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsLocksTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3631
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsLocksTabComponent, isStandalone: true, selector: "et-query-devtools-locks-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <p class=\"et-query-devtools-hint et-query-devtools-locks-hint\">\n The\n <a href=\"https://developer.mozilla.org/docs/Web/API/Web_Locks_API\" target=\"_blank\" rel=\"noreferrer\">Web Locks</a>\n held across this whole origin - every other tab, worker and service worker included, which is the one thing in\n this panel that is not about this tab alone. Read only: a tab cannot release another tab's lock. Polled once a\n second while this tab is open and the page is visible.\n </p>\n </div>\n\n @if (host.lockRows(); as rows) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Lock</th>\n <th>Kind</th>\n <th>Taking part</th>\n <th>This tab</th>\n </tr>\n </thead>\n <tbody>\n @for (row of rows; track row.name) {\n <tr>\n <td [title]=\"row.name\">\n <span class=\"et-query-devtools-lock-name\">\n <span class=\"et-query-devtools-url\">{{ row.label }}</span>\n @if (row.channel) {\n <span class=\"et-query-devtools-count\">{{ row.channel }}</span>\n }\n </span>\n </td>\n <td>{{ kindLabel(row.kind) }}</td>\n <td>{{ row.tabs }}</td>\n <td>\n @let standing = host.lockStanding(row);\n <span\n [class.et-query-devtools-chip--success]=\"standing.tone === 'success'\"\n [class.et-query-devtools-chip--muted]=\"standing.tone === 'muted'\"\n [title]=\"standing.title\"\n class=\"et-query-devtools-chip\"\n >\n {{ standing.label }}\n </span>\n </td>\n </tr>\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"4\">\n Nothing holds a lock on this origin. Multi-tab auth sync and cache polling both take one - without either,\n there is nothing to coordinate.\n </td>\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <p class=\"et-query-devtools-empty\">\n This browser has no Web Locks API, so nothing is coordinated across tabs at all: every tab refreshes its own\n tokens and polls its own cache keys.\n </p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-locks-hint{max-inline-size:80ch}.et-query-devtools-lock-name{display:flex;align-items:center;gap:.8rem}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
3368
3632
  }
3369
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsLocksTabComponent, decorators: [{
3633
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsLocksTabComponent, decorators: [{
3370
3634
  type: Component,
3371
3635
  args: [{ selector: 'et-query-devtools-locks-tab', encapsulation: ViewEncapsulation.None, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <p class=\"et-query-devtools-hint et-query-devtools-locks-hint\">\n The\n <a href=\"https://developer.mozilla.org/docs/Web/API/Web_Locks_API\" target=\"_blank\" rel=\"noreferrer\">Web Locks</a>\n held across this whole origin - every other tab, worker and service worker included, which is the one thing in\n this panel that is not about this tab alone. Read only: a tab cannot release another tab's lock. Polled once a\n second while this tab is open and the page is visible.\n </p>\n </div>\n\n @if (host.lockRows(); as rows) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Lock</th>\n <th>Kind</th>\n <th>Taking part</th>\n <th>This tab</th>\n </tr>\n </thead>\n <tbody>\n @for (row of rows; track row.name) {\n <tr>\n <td [title]=\"row.name\">\n <span class=\"et-query-devtools-lock-name\">\n <span class=\"et-query-devtools-url\">{{ row.label }}</span>\n @if (row.channel) {\n <span class=\"et-query-devtools-count\">{{ row.channel }}</span>\n }\n </span>\n </td>\n <td>{{ kindLabel(row.kind) }}</td>\n <td>{{ row.tabs }}</td>\n <td>\n @let standing = host.lockStanding(row);\n <span\n [class.et-query-devtools-chip--success]=\"standing.tone === 'success'\"\n [class.et-query-devtools-chip--muted]=\"standing.tone === 'muted'\"\n [title]=\"standing.title\"\n class=\"et-query-devtools-chip\"\n >\n {{ standing.label }}\n </span>\n </td>\n </tr>\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"4\">\n Nothing holds a lock on this origin. Multi-tab auth sync and cache polling both take one - without either,\n there is nothing to coordinate.\n </td>\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <p class=\"et-query-devtools-empty\">\n This browser has no Web Locks API, so nothing is coordinated across tabs at all: every tab refreshes its own\n tokens and polls its own cache keys.\n </p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-locks-hint{max-inline-size:80ch}.et-query-devtools-lock-name{display:flex;align-items:center;gap:.8rem}}\n"] }]
3372
3636
  }] });
@@ -3432,65 +3696,246 @@ const summarizeLocks = (options) => {
3432
3696
 
3433
3697
  // Generated from package.json by tools/scripts/generate-version.js - do not edit.
3434
3698
  /** The version of `@ethlete/query-devtools` this build was cut from. */
3435
- const QUERY_DEVTOOLS_VERSION = '1.0.0-next.10';
3699
+ const QUERY_DEVTOOLS_VERSION = '1.0.0-next.12';
3436
3700
 
3437
- /* eslint-disable @typescript-eslint/naming-convention -- the `__export_*` keys are Insomnia's file format */
3438
- const WORKSPACE_ID = 'wrk_ethlete_query_devtools';
3439
- const jsonBody = (body) => {
3440
- try {
3441
- return JSON.stringify(body, null, 2);
3701
+ /**
3702
+ * How deep a value is walked before it is replaced by a marker, and how much of a long string or a long
3703
+ * array survives. A report is read by a person, so a 5000-item list is worth two entries and a count.
3704
+ */
3705
+ const MAX_DEPTH = 6;
3706
+ const MAX_STRING = 200;
3707
+ const MAX_ARRAY_ITEMS = 2;
3708
+ /** What a redacted credential is written as, so a reader can tell it from a value the app never sent. */
3709
+ const REDACTED_SECRET = '[redacted: credential]';
3710
+ /** What the args, response and error of a bearer auth provider's own query are written as. */
3711
+ const REDACTED_AUTH_QUERY = '[redacted: auth query]';
3712
+ /**
3713
+ * Key fragments that name a credential. Matched against the key with every separator removed, so
3714
+ * `accessToken`, `access_token`, `X-Api-Key` and `set-cookie` all reduce to the same word.
3715
+ */
3716
+ const SECRET_KEY_FRAGMENTS = [
3717
+ 'apikey',
3718
+ 'accesskey',
3719
+ 'authorization',
3720
+ 'bearer',
3721
+ 'cookie',
3722
+ 'credential',
3723
+ 'jwt',
3724
+ 'passphrase',
3725
+ 'passwd',
3726
+ 'password',
3727
+ 'privatekey',
3728
+ 'pwd',
3729
+ 'secret',
3730
+ 'sessionid',
3731
+ 'token',
3732
+ ];
3733
+ /**
3734
+ * Whether a key names a credential. The one rule both the session report and the Insomnia export
3735
+ * redact by, so a value the report would blank out cannot travel in a collection instead.
3736
+ */
3737
+ const isSecretKey = (key) => {
3738
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, '');
3739
+ return SECRET_KEY_FRAGMENTS.some((fragment) => normalized.includes(fragment));
3740
+ };
3741
+ /**
3742
+ * Booleans and numbers under a credential-named key are kept: `hasAccessToken: true` and
3743
+ * `expiresIn: 900` are what a report is for, and neither can carry the credential itself.
3744
+ */
3745
+ const holdsSecret = (value) => typeof value === 'string' ? value.length > 0 : !!value && typeof value === 'object';
3746
+ /**
3747
+ * Slims a value for a shareable report: long strings are truncated and long arrays keep only the first
3748
+ * couple of entries, replacing the repetitive tail with a `… (N more)` marker, so a big response
3749
+ * collapses to a representative sample. A `Date`, `Map`, `Set`, `Error` or `bigint` is written as
3750
+ * something a reader can still read. Anything under a credential-named key is replaced by
3751
+ * {@link REDACTED_SECRET} instead - a report travels to a ticket, so a password or a bearer token must
3752
+ * not be in it however deep in a body it sits.
3753
+ */
3754
+ const slimForReport = (value, depth = 0) => {
3755
+ if (typeof value === 'string')
3756
+ return value.length > MAX_STRING ? `${value.slice(0, MAX_STRING)}…` : value;
3757
+ // `JSON.stringify` throws on a bigint, which would take the whole export down with it.
3758
+ if (typeof value === 'bigint')
3759
+ return `${value}`;
3760
+ if (depth > MAX_DEPTH)
3761
+ return '…';
3762
+ if (value instanceof Date)
3763
+ return Number.isNaN(value.getTime()) ? 'Invalid Date' : value.toISOString();
3764
+ if (value instanceof Error) {
3765
+ return { name: value.name, message: value.message, stack: slimForReport(value.stack, depth + 1) };
3766
+ }
3767
+ // `Object.entries` on a `Map`, a `Set` or an `Error` is `[]`, so the branch below would write the one
3768
+ // field a report exists for as `{}`.
3769
+ if (value instanceof Map)
3770
+ return slimForReport([...value], depth);
3771
+ if (value instanceof Set)
3772
+ return slimForReport([...value], depth);
3773
+ if (Array.isArray(value)) {
3774
+ if (value.length > MAX_ARRAY_ITEMS + 1) {
3775
+ return [
3776
+ ...value.slice(0, MAX_ARRAY_ITEMS).map((entry) => slimForReport(entry, depth + 1)),
3777
+ `… (${value.length - MAX_ARRAY_ITEMS} more)`,
3778
+ ];
3779
+ }
3780
+ return value.map((entry) => slimForReport(entry, depth + 1));
3442
3781
  }
3443
- catch {
3444
- // A body holding a circular reference or a throwing `toJSON` cannot be exported.
3445
- return null;
3782
+ if (value && typeof value === 'object') {
3783
+ const out = {};
3784
+ for (const [entryKey, entry] of Object.entries(value)) {
3785
+ out[entryKey] = isSecretKey(entryKey) && holdsSecret(entry) ? REDACTED_SECRET : slimForReport(entry, depth + 1);
3786
+ }
3787
+ return out;
3446
3788
  }
3789
+ return value;
3447
3790
  };
3448
3791
  /**
3449
- * Insomnia's GraphQL body editor stores `{ query, variables }` as its text, so a GraphQL request is
3450
- * exported with the document and its variables rather than the serialized POST body.
3792
+ * The key an entry is matched by when deciding whether it is one of a bearer auth provider's own login
3793
+ * or token-refresh queries: those are ordinary registered queries, and the client, method and route are
3794
+ * what the provider's description and the query's own registration both carry. A GraphQL entry's method
3795
+ * reads `GQL POST` in the panel and `POST` in the provider's description, so the prefix is dropped.
3451
3796
  */
3452
- const graphqlBody = (gqlQuery, body) => {
3453
- const variables = body?.variables ?? {};
3454
- return jsonBody({ query: gqlQuery, variables });
3455
- };
3456
- const bodyOf = (request) => {
3457
- if (request.gqlQuery) {
3458
- const text = graphqlBody(request.gqlQuery, request.body);
3459
- return text === null ? {} : { mimeType: 'graphql', text };
3797
+ const sessionAuthQueryKey = (parts) => `${parts.client ?? ''}|${(parts.method ?? '').toUpperCase().replace(/^GQL /, '')}|${parts.route ?? ''}`;
3798
+ /** Slims the free-form value fields of an entry, leaving the rest as it was collected. */
3799
+ const slimEntry = (entry, authQueryKeys) => {
3800
+ if (entry.kind === 'query' && authQueryKeys.has(sessionAuthQueryKey(entry))) {
3801
+ return {
3802
+ ...entry,
3803
+ ...('args' in entry ? { args: REDACTED_AUTH_QUERY } : {}),
3804
+ ...('response' in entry ? { response: REDACTED_AUTH_QUERY } : {}),
3805
+ ...('error' in entry ? { error: REDACTED_AUTH_QUERY } : {}),
3806
+ ...(entry.detail ? { detail: { redacted: REDACTED_AUTH_QUERY } } : {}),
3807
+ };
3460
3808
  }
3461
- if (request.body === null || request.body === undefined)
3809
+ return {
3810
+ ...entry,
3811
+ ...('args' in entry ? { args: slimForReport(entry.args) } : {}),
3812
+ ...('response' in entry ? { response: slimForReport(entry.response) } : {}),
3813
+ ...('error' in entry ? { error: slimForReport(entry.error) } : {}),
3814
+ ...(entry.detail ? { detail: slimForReport(entry.detail) } : {}),
3815
+ ...(entry.overrides?.length
3816
+ ? { overrides: slimForReport(entry.overrides) }
3817
+ : {}),
3818
+ };
3819
+ };
3820
+ /**
3821
+ * Builds the whole-session report: every registered entry with what it ran and what it holds, the event
3822
+ * log, the cache totals per client and anything armed in the Faults or Mocks tabs or as a response
3823
+ * override.
3824
+ *
3825
+ * Bodies are slimmed rather than dumped in full - the point is a file small enough to attach to a bug
3826
+ * report, and a 4 MB response says nothing a representative sample does not. Armed faults and overrides
3827
+ * are included because a report captured while the panel was lying to the app has to say so. Neither
3828
+ * survives past the current page - this is a snapshot for a bug report, not something the panel can
3829
+ * later import to restore a session.
3830
+ *
3831
+ * Credentials are the exception to "slimmed, not redacted": an auth provider's own queries are reported
3832
+ * without what they sent or received, and any credential-named key anywhere else in the file is replaced
3833
+ * by {@link REDACTED_SECRET}.
3834
+ */
3835
+ const buildQueryDevtoolsSessionExport = (options) => {
3836
+ const authQueryKeys = new Set(options.authQueryKeys ?? []);
3837
+ return {
3838
+ _type: 'ethlete.query:devtools-session',
3839
+ exportedAt: new Date(options.now).toISOString(),
3840
+ location: options.location,
3841
+ about: options.about,
3842
+ counts: {
3843
+ clients: options.clients.length,
3844
+ entries: options.entries.length,
3845
+ events: options.events.length,
3846
+ armedFaults: options.faults.length,
3847
+ armedOverrides: options.entries.reduce((sum, entry) => sum + (entry.overrides?.length ?? 0), 0),
3848
+ armedMocks: options.mocks.length,
3849
+ },
3850
+ clients: options.clients,
3851
+ entries: options.entries.map((entry) => slimEntry(entry, authQueryKeys)),
3852
+ events: options.events,
3853
+ faults: options.faults,
3854
+ mocks: options.mocks,
3855
+ };
3856
+ };
3857
+
3858
+ /* eslint-disable @typescript-eslint/naming-convention -- the `__export_*` keys are Insomnia's file format */
3859
+ const WORKSPACE_ID = 'wrk_ethlete_query_devtools';
3860
+ const bodyOf = (request, body) => {
3861
+ if (body.data === null)
3462
3862
  return {};
3463
- const text = jsonBody(request.body);
3464
- return text === null ? {} : { mimeType: 'application/json', text };
3863
+ // Insomnia's GraphQL body editor stores `{ query, variables }` as its text, so a GraphQL request
3864
+ // carries the document and its variables rather than the serialized POST body.
3865
+ if (request.gqlQuery)
3866
+ return { mimeType: 'graphql', text: body.data };
3867
+ return body.json ? { mimeType: 'application/json', text: body.data } : { text: body.data };
3465
3868
  };
3466
3869
  /**
3467
3870
  * Angular's `HttpClient` labels an object body `application/json` on its own, so a replay outside the
3468
- * app needs the header spelled out to reach the same endpoint the same way.
3871
+ * app needs the header spelled out to reach the same endpoint the same way. Only a body that was
3872
+ * serialized as JSON gets it: Angular sends a string as `text/plain` and lets the browser label the
3873
+ * rest.
3469
3874
  */
3470
- const headersOf = (input, hasBody) => {
3875
+ const headersOf = (input, isJson) => {
3471
3876
  const headers = input.headers.filter((header) => header.value !== '');
3472
3877
  const hasContentType = headers.some((header) => header.name.toLowerCase() === 'content-type');
3473
- if (!hasBody || hasContentType)
3878
+ if (!isJson || hasContentType)
3474
3879
  return headers;
3475
3880
  return [...headers, { name: 'Content-Type', value: 'application/json' }];
3476
3881
  };
3882
+ /** What a request says about itself when what it sends is not what the app sent. */
3883
+ const notesOf = (body, droppedAuth) => {
3884
+ const notes = [];
3885
+ if (body.binary)
3886
+ notes.push(`The panel cannot replay a ${body.binary} body - this request sends none.`);
3887
+ if (body.unserializable)
3888
+ notes.push('The panel could not serialize this body - this request sends none.');
3889
+ if (droppedAuth) {
3890
+ notes.push('The Authorization this request was sent with is left out: no token refresh could be exported for its ' +
3891
+ 'auth provider, and a token frozen at export time is stale within the hour.');
3892
+ }
3893
+ return notes.join(' ');
3894
+ };
3477
3895
  const AUTH_HEADER = 'Authorization';
3478
3896
  /**
3479
3897
  * Insomnia's template tag for reading a value out of another request's response. `when-expired` plus a
3480
3898
  * max age is what makes the chain maintain itself: once the stored refresh response is older than
3481
3899
  * `maxAgeSeconds`, sending a request that reads from it re-sends the refresh first.
3482
3900
  */
3483
- const responseTag = (requestId, refresh) => `{% response 'body', '${requestId}', '${refresh.accessTokenPath}', 'when-expired', ${refresh.maxAgeSeconds} %}`;
3901
+ const responseTag = (requestId, refresh) => `{% response 'body', '${requestId}', '${escapeTag(refresh.accessTokenPath)}', 'when-expired', ${refresh.maxAgeSeconds} %}`;
3902
+ /** A value going inside single quotes - a template tag argument or a bracket-quoted JSONPath key. */
3903
+ const escapeTag = (value) => value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
3484
3904
  /** Replaces whatever `Authorization` the export resolved with the token the refresh chain yields. */
3485
3905
  const withChainedAuth = (headers, value) => [
3486
3906
  { name: AUTH_HEADER, value },
3487
3907
  ...headers.filter((header) => header.name.toLowerCase() !== AUTH_HEADER.toLowerCase()),
3488
3908
  ];
3909
+ const IDENTIFIER_KEY = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
3910
+ const valuePath = (value, node) => {
3911
+ if (node.value === value)
3912
+ return node.path;
3913
+ if (node.depth > 5 || !node.value || typeof node.value !== 'object')
3914
+ return null;
3915
+ for (const [key, entry] of Object.entries(node.value)) {
3916
+ const path = Array.isArray(node.value)
3917
+ ? `${node.path}[${key}]`
3918
+ : IDENTIFIER_KEY.test(key)
3919
+ ? `${node.path}.${key}`
3920
+ : `${node.path}['${escapeTag(key)}']`;
3921
+ const found = valuePath(value, { value: entry, path, depth: node.depth + 1 });
3922
+ if (found)
3923
+ return found;
3924
+ }
3925
+ return null;
3926
+ };
3927
+ /**
3928
+ * The JSONPath of a string value inside a response, or `null`. Used to locate the access token in an
3929
+ * auth response whose shape only the provider's `extractTokens` knows. A key JSONPath cannot name
3930
+ * after a dot is bracket-quoted, since `$.access-token` resolves as a subtraction rather than a member.
3931
+ */
3932
+ const findInsomniaValuePath = (value, response) => valuePath(value, { value: response, path: '$', depth: 0 });
3489
3933
  /**
3490
3934
  * Builds an Insomnia v4 collection from a set of requests, ready to be imported via Insomnia's
3491
3935
  * `Import > From File / Clipboard`. Requests are filed into a folder per query client, and a request
3492
3936
  * naming a token refresh gets its bearer token from that refresh's response rather than from a
3493
- * literal token frozen at export time.
3937
+ * literal token frozen at export time. A secure request whose refresh is not in the collection is
3938
+ * exported without its credentials, and says so in its description.
3494
3939
  */
3495
3940
  const buildInsomniaExport = (options) => {
3496
3941
  const groups = new Map();
@@ -3549,7 +3994,7 @@ const buildInsomniaExport = (options) => {
3549
3994
  // Emitted first so every chained `Authorization` below already has a request id to point at.
3550
3995
  options.tokenRefreshes?.forEach((refresh, index) => {
3551
3996
  const id = `req_refresh_${index}`;
3552
- const body = bodyOf(refresh);
3997
+ const resolved = queryDevtoolsRequestBody(refresh, 2);
3553
3998
  chainedAuth.set(refresh.id, `Bearer ${responseTag(id, refresh)}`);
3554
3999
  requestResource({
3555
4000
  _id: id,
@@ -3560,23 +4005,28 @@ const buildInsomniaExport = (options) => {
3560
4005
  'the body is the one the app held at export time - re-export once it is spent.',
3561
4006
  method: refresh.method,
3562
4007
  url: refresh.url,
3563
- body,
3564
- headers: headersOf(refresh, 'text' in body),
4008
+ body: bodyOf(refresh, resolved),
4009
+ headers: headersOf(refresh, resolved.json),
3565
4010
  // Insomnia sorts ascending and the exported requests start at 0, so the refresh stays on top.
3566
4011
  metaSortKey: index - (options.tokenRefreshes?.length ?? 0),
3567
4012
  });
3568
4013
  });
3569
4014
  options.requests.forEach((request, index) => {
3570
- const body = bodyOf(request);
3571
- const headers = headersOf(request, 'text' in body);
4015
+ const resolved = queryDevtoolsRequestBody(request, 2);
3572
4016
  const auth = request.secureBy ? chainedAuth.get(request.secureBy) : undefined;
4017
+ // A secure request the collection cannot mint a token for keeps none of the credentials the panel
4018
+ // resolved for it: a live bearer token in a file bound for a ticket is the leak the chaining exists
4019
+ // to avoid, and it is stale within the hour anyway.
4020
+ const dropCredentials = !!request.secureBy && !auth;
4021
+ const headers = headersOf(request, resolved.json).filter((header) => !dropCredentials || !isSecretKey(header.name));
3573
4022
  requestResource({
3574
4023
  _id: `req_${index}`,
3575
4024
  parentId: groupIdFor(request.group),
3576
4025
  name: request.name,
4026
+ description: notesOf(resolved, dropCredentials),
3577
4027
  method: request.method,
3578
4028
  url: request.url,
3579
- body,
4029
+ body: bodyOf(request, resolved),
3580
4030
  headers: auth ? withChainedAuth(headers, auth) : headers,
3581
4031
  metaSortKey: index,
3582
4032
  });
@@ -3818,7 +4268,7 @@ class QueryDevtoolsQueriesTabComponent {
3818
4268
  return this.queryGroups().find((group) => group.key === key)?.head.entry.id === this.host.selectedQueryId();
3819
4269
  }, /* @ts-ignore */
3820
4270
  ...(ngDevMode ? [{ debugName: "selectedIsFoldHead" }] : /* istanbul ignore next */ []));
3821
- /** How many queries the list would hold with the search box empty, which is what the count compares to. */
4271
+ /** How many queries are in scope before the search box and the status chips narrow them, tombstones as the `gone` chip decides. */
3822
4272
  this.scopedQueryCount = computed(() => {
3823
4273
  const facets = this.host.queryFacets();
3824
4274
  return this.host.scopedQueries().filter((entry) => !entry.destroyedAt || this.listsTombstone(entry, facets)).length;
@@ -3999,10 +4449,10 @@ class QueryDevtoolsQueriesTabComponent {
3999
4449
  const parts = [entry.meta.method, route, url ? this.host.requestPath(url) : null];
4000
4450
  return parts.join(' ').toLowerCase();
4001
4451
  }
4002
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsQueriesTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4003
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsQueriesTabComponent, isStandalone: true, selector: "et-query-devtools-queries-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!host.inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n <!-- Search leads: it is what the list is narrowed with most of the time. The scope controls sit\n after it, and the count reads as the search's result rather than as one more control. -->\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #queryFilterInput\n [value]=\"host.queryFilter()\"\n (input)=\"host.queryFilter.set(queryFilterInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter method / route / url\u2026\"\n aria-label=\"Filter queries\"\n />\n @if (host.inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n <button\n (click)=\"host.clearInspectFilter()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the inspection filter and show all queries\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Clear\n </button>\n } @else {\n <select\n #clientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Client\"\n >\n <option value=\"\">All clients</option>\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n }\n <span class=\"et-query-devtools-count\">\n @if (isQueryListNarrowed()) {\n {{ filteredQueries().length }} of {{ scopedQueryCount() }}\n } @else {\n {{ scopedQueryCount() }}\n }\n </span>\n <button\n [disabled]=\"!filteredQueries().length\"\n (click)=\"downloadInsomniaCollection()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download the listed queries as an Insomnia collection, with the auth provider's token refresh\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Insomnia\n </button>\n </div>\n\n <div class=\"et-query-devtools-toolbar-row\">\n @let recentFirst = host.queryRecentFirst();\n <div class=\"et-query-devtools-btn-group\">\n <button\n [title]=\"\n (recentFirst ? 'Newest run first.' : 'Oldest run first.') +\n ' Click to reverse. Queries that have never run stay at the bottom either way.'\n \"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n recent <span aria-hidden=\"true\">{{ recentFirst ? '\u2193' : '\u2191' }}</span>\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n [title]=\"\n treeView\n ? 'Grouped by route path. Click for the flat list.'\n : 'Flat list. Click to group by route path - segments nothing branches off are folded into one row.'\n \"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2442</span> tree\n </button>\n </div>\n <!-- Only the statuses something is actually in. Five chips, four of them reading `0`, were four\n controls that could not be pressed taking up the row that says what is wrong. -->\n @for (facet of visibleFacets(); track facet.id) {\n <button\n [class.et-query-devtools-facet--active]=\"facet.active\"\n [attr.aria-pressed]=\"facet.active\"\n (click)=\"host.toggleFacet(facet.id)\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n >\n {{ facet.label }}\n <span class=\"et-query-devtools-facet-count\">{{ facet.count }}</span>\n </button>\n }\n @let goneListed = listedGoneQueries().length;\n @if (goneListed) {\n <button\n (click)=\"forgetGoneQueries()\"\n class=\"et-query-devtools-forget\"\n type=\"button\"\n title=\"Drop the {{ goneListed }} destroyed queries listed below - they cannot be brought back\"\n >\n Forget {{ goneListed }}\n </button>\n }\n @if (isQueryListNarrowed()) {\n <button\n (click)=\"host.clearQueryFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the search term and the status chips\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n </div>\n </div>\n\n @if (host.queryTreeView()) {\n @for (row of queryTree(); track row.key) {\n @if (row.kind === 'folder') {\n <div [style.--_et-qdt-tree-depth]=\"row.depth\" class=\"et-query-devtools-row et-query-devtools-row--folder\">\n <button\n [attr.aria-expanded]=\"!row.collapsed\"\n [title]=\"\n (row.collapsed ? 'Expand' : 'Collapse') + ' /' + row.label + ' - ' + row.count + ' queries below it'\n \"\n (click)=\"host.toggleQueryPath(row.key)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ row.collapsed ? '\u25B8' : '\u25BE' }}</span>\n <span class=\"et-query-devtools-folder\">/{{ row.label }}</span>\n <span class=\"et-query-devtools-row-count\">{{ row.count }}</span>\n </button>\n </div>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"queryGroupBlock\"\n [ngTemplateOutletContext]=\"{ group: row.item, depth: row.depth, trim: row.parentPath }\"\n />\n }\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n } @else {\n @for (group of queryGroups(); track group.key) {\n <ng-container [ngTemplateOutlet]=\"queryGroupBlock\" [ngTemplateOutletContext]=\"{ group, depth: 0, trim: '' }\" />\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n }\n\n <!-- An empty scope is a different problem from a filter that matches nothing, and only the\n latter is the user's own doing - so it names the way out. -->\n <ng-template #emptyList>\n @if (scopedQueryCount()) {\n <p class=\"et-query-devtools-empty\">No query matches the filter \u00B7 {{ scopedQueryCount() }} in scope.</p>\n } @else if (goneQueryCount()) {\n <p class=\"et-query-devtools-empty\">\n No query in scope still exists \u00B7 pick Gone to list the {{ goneQueryCount() }} destroyed.\n </p>\n } @else if (host.inspectFilterIds()) {\n <p class=\"et-query-devtools-empty\">\n The inspected element's queries are no longer registered. Clear the filter.\n </p>\n } @else {\n <p class=\"et-query-devtools-empty\">No queries registered.</p>\n }\n </ng-template>\n\n <ng-template #queryGroupBlock let-group=\"group\" let-depth=\"depth\" let-trim=\"trim\">\n @if (batchEntryOf(group); as batchEntry) {\n <ng-container\n [ngTemplateOutlet]=\"batchGroupBlock\"\n [ngTemplateOutletContext]=\"{ group, batchEntry, depth, trim }\"\n />\n } @else if (group.items.length === 1) {\n <ng-container [ngTemplateOutlet]=\"queryRow\" [ngTemplateOutletContext]=\"{ item: group.head, depth, trim }\" />\n } @else {\n @let expanded = isGroupExpanded(group);\n @let head = group.head;\n @let groupGone = !!head.entry.destroyedAt;\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <!-- The caret is its own control: every row in the fold is the same request, so opening the\n fold and inspecting what it holds are two different intentions. -->\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n [title]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"Inspect this request - all {{\n group.items.length\n }} consumers share it, so any of them answers for it\"\n >\n <span\n [attr.data-status]=\"groupGone ? null : groupStatus(group)\"\n [title]=\"groupGone ? 'destroyed' : groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ head.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(head.entry, head.query)\" [trimPrefix]=\"trim\" />\n </span>\n @if (groupGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"These queries no longer exist - what you see is the last state they held, frozen\"\n >\n gone\n </span>\n }\n @if (!groupGone && isGroupStale(group)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (isGroupTampered(group)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"At least one of these is showing an armed response override or a devtools-injected failure\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Consumers of this one request\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, ordinal: $index + 1, depth, trim }\"\n />\n }\n }\n }\n </ng-template>\n\n <!-- A bulk run's items, folded under the run rather than under what a row shows. They only exist\n while in flight - the batch destroys each item's query as it settles - so this row stands for\n the whole run, and the Batches tab is where its settled items are. -->\n <ng-template #batchGroupBlock let-group=\"group\" let-batchEntry=\"batchEntry\" let-depth=\"depth\" let-trim=\"trim\">\n @let batch = host.asBatch(batchEntry);\n @let expanded = isGroupExpanded(group);\n @let toggleLabel =\n (expanded ? 'Collapse' : 'Expand') + ' the ' + group.items.length + ' requests this batch has in flight';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"toggleLabel\"\n [title]=\"toggleLabel\"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"One item of a batch run - {{ batch.completed() }} of {{\n batch.total()\n }} settled. The Batches tab has the whole run.\"\n >\n <span\n [attr.data-status]=\"groupStatus(group)\"\n [title]=\"groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ batchEntry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(batchEntry)\" [trimPrefix]=\"trim\" />\n </span>\n <span\n class=\"et-query-devtools-chip\"\n title=\"Registered by createQueryBatch - inspect the run on the Batches tab\"\n >\n batch {{ batch.completed() }}/{{ batch.total() }}\n </span>\n @if (batch.failed()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">{{ batch.failed() }} failed</span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Requests this batch has in flight\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(group.head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, showRoute: true, depth, trim }\"\n />\n }\n }\n </ng-template>\n\n <ng-template\n #queryRow\n let-item=\"item\"\n let-nested=\"nested\"\n let-showRoute=\"showRoute\"\n let-ordinal=\"ordinal\"\n let-depth=\"depth\"\n let-trim=\"trim\"\n >\n @let isGone = !!item.entry.destroyedAt;\n @let isPinned = host.isQueryPinned(item.entry);\n @let pinLabel = isPinned ? 'Unpin this query' : 'Pin this query to the top of the list';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"host.selectedQueryId() === item.entry.id\"\n [class.et-query-devtools-row--gone]=\"isGone\"\n [class.et-query-devtools-row--nested]=\"nested\"\n class=\"et-query-devtools-row\"\n >\n <button (click)=\"host.selectedQueryId.set(item.entry.id)\" class=\"et-query-devtools-row-select\" type=\"button\">\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\"></span>\n <span\n [attr.data-status]=\"isGone ? null : host.queryStatus(item.query)\"\n [title]=\"isGone ? 'destroyed' : host.queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n <!-- Inside a fold the route is the fold's own head, repeated once per consumer. Where the query\n was created is the only thing that differs, so that is what the row says instead. A batch\n fold is the exception: its items differ in the route, which is why `showRoute` is set. -->\n @if (nested && !showRoute) {\n <span class=\"et-query-devtools-consumer\">\n @if (consumerLabel(item); as consumer) {\n &lt;{{ consumer }}&gt;\n } @else {\n created outside a component\n }\n </span>\n <span class=\"et-query-devtools-row-count\">#{{ ordinal }}</span>\n } @else {\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(item.entry, item.query)\" [trimPrefix]=\"trim\" />\n </span>\n }\n @if (isGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"This query no longer exists - what you see is the last state it held, frozen\"\n >\n gone\n </span>\n }\n <!-- A retry is the one thing a loading dot actively misrepresents: it reads as a request\n on its way when nothing is in flight at all. -->\n @if (!isGone && host.requestProgress(item.query)?.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Waiting out a retry backoff before attempt {{ retry.attempt }}\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ retry.attempt }}\n </span>\n }\n @if (!isGone && host.isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (host.isTampered(item.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(item)) }}</span>\n </button>\n <button\n [class.et-query-devtools-row-pin--active]=\"isPinned\"\n [title]=\"pinLabel\"\n [attr.aria-label]=\"pinLabel\"\n [attr.aria-pressed]=\"isPinned\"\n (click)=\"host.toggleQueryPin(item.entry)\"\n class=\"et-query-devtools-row-pin\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">{{ isPinned ? '\u2605' : '\u2606' }}</span>\n </button>\n </div>\n </ng-template>\n </div>\n\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'list', container: panes })\"\n (dblclick)=\"host.resetPaneSize('list')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <div class=\"et-query-devtools-detail\">\n @if (host.selectedQuery(); as sel) {\n <et-query-devtools-detail [sel]=\"sel\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 var(--_et-qdt-list-width, 34%);min-inline-size:var(--_et-qdt-list-width, 22rem);max-inline-size:var(--_et-qdt-list-width, 40rem);overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-forget{appearance:none;display:inline-flex;align-items:center;gap:.5rem;min-block-size:var(--_et-qdt-control-h);border:1px solid color-mix(in srgb,var(--_et-qdt-error) 50%,transparent);border-radius:var(--_et-qdt-radius);background:color-mix(in srgb,var(--_et-qdt-error) 10%,transparent);color:var(--_et-qdt-error);cursor:pointer;padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-forget:hover{border-color:var(--_et-qdt-error);background:color-mix(in srgb,var(--_et-qdt-error) 22%,transparent)}}.et-query-devtools-list .et-query-devtools-toolbar-row .et-query-devtools-search{flex:1 1 100%}.et-query-devtools-toolbar--filtered{background:linear-gradient(to right,color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent),transparent 65%),var(--_et-qdt-bg);box-shadow:inset .2rem 0 0 var(--_et-qdt-accent),inset 0 -1px color-mix(in srgb,var(--_et-qdt-accent) 30%,transparent)}.et-query-devtools-row-select{display:flex;flex:1 1 auto;align-items:center;gap:.8rem;min-inline-size:0;text-align:start;appearance:none;border:none;background:transparent;color:inherit;padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row-select>.et-query-devtools-chip{padding-block:.1rem;line-height:1.2}.et-query-devtools-row-pin{display:flex;flex:0 0 auto;align-self:center;align-items:center;justify-content:center;inline-size:3.2rem;block-size:3.2rem;margin-inline-end:.4rem;appearance:none;border:none;border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font-family:inherit;font-size:1.8rem;line-height:1;opacity:0}.et-query-devtools-row-pin:focus-visible,.et-query-devtools-row-pin--active{opacity:1}@media(hover:hover){.et-query-devtools-row:hover .et-query-devtools-row-pin{opacity:1}}@media(hover:none){.et-query-devtools-row-pin{opacity:1}}@media(hover:hover){.et-query-devtools-row-pin:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-row-pin--active,.et-query-devtools-row-pin--active:hover{color:var(--_et-qdt-accent)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row--gone{color:var(--_et-qdt-muted)}.et-query-devtools-row-caret{flex:0 0 auto;inline-size:1rem;color:var(--_et-qdt-subtle);font-size:1rem;line-height:1}.et-query-devtools-row-caret-btn{display:flex;flex:0 0 auto;align-items:center;appearance:none;border:none;background:transparent;color:inherit;cursor:pointer;padding-block:0;padding-inline:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem) 0;font:inherit}@media(hover:hover){.et-query-devtools-row-caret-btn:hover .et-query-devtools-row-caret{color:var(--_et-qdt-fg)}}.et-query-devtools-row>.et-query-devtools-row-select{padding-inline-start:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--nested>.et-query-devtools-row-select{padding-inline-start:calc(2.6rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--group>.et-query-devtools-row-select{padding-inline:.8rem 4.4rem}.et-query-devtools-consumer{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--folder{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-folder{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--group{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-row--group.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row-count{flex:0 0 auto;color:var(--_et-qdt-subtle);font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-row-time{flex:0 0 auto;inline-size:8ch;text-align:end;color:var(--_et-qdt-subtle);font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-sort{flex:0 0 auto;color:var(--_et-qdt-muted)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: QueryDevtoolsDetailComponent, selector: "et-query-devtools-detail", inputs: ["sel"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
4452
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsQueriesTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4453
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsQueriesTabComponent, isStandalone: true, selector: "et-query-devtools-queries-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!host.inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n <!-- Search leads: it is what the list is narrowed with most of the time. The scope controls sit\n after it, and the count reads as the search's result rather than as one more control. -->\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #queryFilterInput\n [value]=\"host.queryFilter()\"\n (input)=\"host.queryFilter.set(queryFilterInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter method / route / url\u2026\"\n aria-label=\"Filter queries\"\n />\n @if (host.inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n <button\n (click)=\"host.clearInspectFilter()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the inspection filter and show all queries\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Clear\n </button>\n } @else {\n <select\n #clientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Client\"\n >\n <option value=\"\">All clients</option>\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n }\n <span class=\"et-query-devtools-count\">\n @if (isQueryListNarrowed()) {\n {{ filteredQueries().length }} of {{ scopedQueryCount() }}\n } @else {\n {{ scopedQueryCount() }}\n }\n </span>\n <button\n [disabled]=\"!filteredQueries().length\"\n (click)=\"downloadInsomniaCollection()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download the listed queries as an Insomnia collection, with the auth provider's token refresh\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Insomnia\n </button>\n </div>\n\n <div class=\"et-query-devtools-toolbar-row\">\n @let recentFirst = host.queryRecentFirst();\n <div class=\"et-query-devtools-btn-group\">\n <button\n [title]=\"\n (recentFirst ? 'Newest run first.' : 'Oldest run first.') +\n ' Click to reverse. Queries that have never run stay at the bottom either way.'\n \"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n recent <span aria-hidden=\"true\">{{ recentFirst ? '\u2193' : '\u2191' }}</span>\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n [title]=\"\n treeView\n ? 'Grouped by route path. Click for the flat list.'\n : 'Flat list. Click to group by route path - segments nothing branches off are folded into one row.'\n \"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2442</span> tree\n </button>\n </div>\n <!-- Only the statuses something is actually in. Five chips, four of them reading `0`, were four\n controls that could not be pressed taking up the row that says what is wrong. -->\n @for (facet of visibleFacets(); track facet.id) {\n <button\n [class.et-query-devtools-facet--active]=\"facet.active\"\n [attr.aria-pressed]=\"facet.active\"\n (click)=\"host.toggleFacet(facet.id)\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n >\n {{ facet.label }}\n <span class=\"et-query-devtools-facet-count\">{{ facet.count }}</span>\n </button>\n }\n @let goneListed = listedGoneQueries().length;\n @if (goneListed) {\n <button\n (click)=\"forgetGoneQueries()\"\n class=\"et-query-devtools-forget\"\n type=\"button\"\n title=\"Drop the {{ goneListed }} destroyed queries listed below - they cannot be brought back\"\n >\n Forget {{ goneListed }}\n </button>\n }\n @if (isQueryListNarrowed()) {\n <button\n (click)=\"host.clearQueryFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the search term and the status chips\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n </div>\n </div>\n\n @if (host.queryTreeView()) {\n @for (row of queryTree(); track row.key) {\n @if (row.kind === 'folder') {\n <div [style.--_et-qdt-tree-depth]=\"row.depth\" class=\"et-query-devtools-row et-query-devtools-row--folder\">\n <button\n [attr.aria-expanded]=\"!row.collapsed\"\n [title]=\"\n (row.collapsed ? 'Expand' : 'Collapse') + ' /' + row.label + ' - ' + row.count + ' queries below it'\n \"\n (click)=\"host.toggleQueryPath(row.key)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ row.collapsed ? '\u25B8' : '\u25BE' }}</span>\n <span class=\"et-query-devtools-folder\">/{{ row.label }}</span>\n <span class=\"et-query-devtools-row-count\">{{ row.count }}</span>\n </button>\n </div>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"queryGroupBlock\"\n [ngTemplateOutletContext]=\"{ group: row.item, depth: row.depth, trim: row.parentPath }\"\n />\n }\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n } @else {\n @for (group of queryGroups(); track group.key) {\n <ng-container [ngTemplateOutlet]=\"queryGroupBlock\" [ngTemplateOutletContext]=\"{ group, depth: 0, trim: '' }\" />\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n }\n\n <!-- An empty scope is a different problem from a filter that matches nothing, and only the\n latter is the user's own doing - so it names the way out. -->\n <ng-template #emptyList>\n @if (scopedQueryCount()) {\n <p class=\"et-query-devtools-empty\">No query matches the filter \u00B7 {{ scopedQueryCount() }} in scope.</p>\n } @else if (goneQueryCount()) {\n <p class=\"et-query-devtools-empty\">\n No query in scope still exists \u00B7 pick Gone to list the {{ goneQueryCount() }} destroyed.\n </p>\n } @else if (host.inspectFilterIds()) {\n <p class=\"et-query-devtools-empty\">\n The inspected element's queries are no longer registered. Clear the filter.\n </p>\n } @else {\n <p class=\"et-query-devtools-empty\">No queries registered.</p>\n }\n </ng-template>\n\n <ng-template #queryGroupBlock let-group=\"group\" let-depth=\"depth\" let-trim=\"trim\">\n @if (batchEntryOf(group); as batchEntry) {\n <ng-container\n [ngTemplateOutlet]=\"batchGroupBlock\"\n [ngTemplateOutletContext]=\"{ group, batchEntry, depth, trim }\"\n />\n } @else if (group.items.length === 1) {\n <ng-container [ngTemplateOutlet]=\"queryRow\" [ngTemplateOutletContext]=\"{ item: group.head, depth, trim }\" />\n } @else {\n @let expanded = isGroupExpanded(group);\n @let head = group.head;\n @let groupGone = !!head.entry.destroyedAt;\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <!-- The caret is its own control: every row in the fold is the same request, so opening the\n fold and inspecting what it holds are two different intentions. -->\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n [title]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"Inspect this request - all {{\n group.items.length\n }} consumers share it, so any of them answers for it\"\n >\n <span\n [attr.data-status]=\"groupGone ? null : groupStatus(group)\"\n [title]=\"groupGone ? 'destroyed' : groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ head.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(head.entry, head.query)\" [trimPrefix]=\"trim\" />\n </span>\n @if (groupGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"These queries no longer exist - what you see is the last state they held, frozen\"\n >\n gone\n </span>\n }\n @if (!groupGone && isGroupStale(group)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (isGroupTampered(group)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"At least one of these is showing an armed response override or a devtools-injected failure\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Consumers of this one request\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, ordinal: $index + 1, depth, trim }\"\n />\n }\n }\n }\n </ng-template>\n\n <!-- A bulk run's items, folded under the run rather than under what a row shows. They only exist\n while in flight - the batch destroys each item's query as it settles - so this row stands for\n the whole run, and the Batches tab is where its settled items are. -->\n <ng-template #batchGroupBlock let-group=\"group\" let-batchEntry=\"batchEntry\" let-depth=\"depth\" let-trim=\"trim\">\n @let batch = host.asBatch(batchEntry);\n @let expanded = isGroupExpanded(group);\n @let toggleLabel =\n (expanded ? 'Collapse' : 'Expand') + ' the ' + group.items.length + ' requests this batch has in flight';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"toggleLabel\"\n [title]=\"toggleLabel\"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"One item of a batch run - {{ batch.completed() }} of {{\n batch.total()\n }} settled. The Batches tab has the whole run.\"\n >\n <span\n [attr.data-status]=\"groupStatus(group)\"\n [title]=\"groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ batchEntry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(batchEntry)\" [trimPrefix]=\"trim\" />\n </span>\n <span\n class=\"et-query-devtools-chip\"\n title=\"Registered by createQueryBatch - inspect the run on the Batches tab\"\n >\n batch {{ batch.completed() }}/{{ batch.total() }}\n </span>\n @if (batch.failed()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">{{ batch.failed() }} failed</span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Requests this batch has in flight\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(group.head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, showRoute: true, depth, trim }\"\n />\n }\n }\n </ng-template>\n\n <ng-template\n #queryRow\n let-item=\"item\"\n let-nested=\"nested\"\n let-showRoute=\"showRoute\"\n let-ordinal=\"ordinal\"\n let-depth=\"depth\"\n let-trim=\"trim\"\n >\n @let isGone = !!item.entry.destroyedAt;\n @let isPinned = host.isQueryPinned(item.entry);\n @let pinLabel = isPinned ? 'Unpin this query' : 'Pin this query to the top of the list';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"host.selectedQueryId() === item.entry.id\"\n [class.et-query-devtools-row--gone]=\"isGone\"\n [class.et-query-devtools-row--nested]=\"nested\"\n class=\"et-query-devtools-row\"\n >\n <button (click)=\"host.selectedQueryId.set(item.entry.id)\" class=\"et-query-devtools-row-select\" type=\"button\">\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\"></span>\n <span\n [attr.data-status]=\"isGone ? null : host.queryStatus(item.query)\"\n [title]=\"isGone ? 'destroyed' : host.queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n <!-- Inside a fold the route is the fold's own head, repeated once per consumer. Where the query\n was created is the only thing that differs, so that is what the row says instead. A batch\n fold is the exception: its items differ in the route, which is why `showRoute` is set. -->\n @if (nested && !showRoute) {\n <span class=\"et-query-devtools-consumer\">\n @if (consumerLabel(item); as consumer) {\n &lt;{{ consumer }}&gt;\n } @else {\n created outside a component\n }\n </span>\n <span class=\"et-query-devtools-row-count\">#{{ ordinal }}</span>\n } @else {\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(item.entry, item.query)\" [trimPrefix]=\"trim\" />\n </span>\n }\n @if (isGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"This query no longer exists - what you see is the last state it held, frozen\"\n >\n gone\n </span>\n }\n <!-- A retry is the one thing a loading dot actively misrepresents: it reads as a request\n on its way when nothing is in flight at all. -->\n @if (!isGone && host.requestProgress(item.query)?.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Waiting out a retry backoff before attempt {{ retry.attempt }}\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ retry.attempt }}\n </span>\n }\n @if (!isGone && host.isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (host.isTampered(item.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(item)) }}</span>\n </button>\n <button\n [class.et-query-devtools-row-pin--active]=\"isPinned\"\n [title]=\"pinLabel\"\n [attr.aria-label]=\"pinLabel\"\n [attr.aria-pressed]=\"isPinned\"\n (click)=\"host.toggleQueryPin(item.entry)\"\n class=\"et-query-devtools-row-pin\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">{{ isPinned ? '\u2605' : '\u2606' }}</span>\n </button>\n </div>\n </ng-template>\n </div>\n\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'list', container: panes })\"\n (dblclick)=\"host.resetPaneSize('list')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <div class=\"et-query-devtools-detail\">\n @if (host.selectedQuery(); as sel) {\n <et-query-devtools-detail [sel]=\"sel\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 var(--_et-qdt-list-width, 34%);min-inline-size:var(--_et-qdt-list-width, 22rem);max-inline-size:var(--_et-qdt-list-width, 40rem);overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-forget{appearance:none;display:inline-flex;align-items:center;gap:.5rem;min-block-size:var(--_et-qdt-control-h);border:1px solid color-mix(in srgb,var(--_et-qdt-error) 50%,transparent);border-radius:var(--_et-qdt-radius);background:color-mix(in srgb,var(--_et-qdt-error) 10%,transparent);color:var(--_et-qdt-error);cursor:pointer;padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-forget:hover{border-color:var(--_et-qdt-error);background:color-mix(in srgb,var(--_et-qdt-error) 22%,transparent)}}.et-query-devtools-list .et-query-devtools-toolbar-row .et-query-devtools-search{flex:1 1 100%}.et-query-devtools-toolbar--filtered{background:linear-gradient(to right,color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent),transparent 65%),var(--_et-qdt-bg);box-shadow:inset .2rem 0 0 var(--_et-qdt-accent),inset 0 -1px color-mix(in srgb,var(--_et-qdt-accent) 30%,transparent)}.et-query-devtools-row-select{display:flex;flex:1 1 auto;align-items:center;gap:.8rem;min-inline-size:0;text-align:start;appearance:none;border:none;background:transparent;color:inherit;padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row-select>.et-query-devtools-chip{padding-block:.1rem;line-height:1.2}.et-query-devtools-row-pin{display:flex;flex:0 0 auto;align-self:center;align-items:center;justify-content:center;inline-size:3.2rem;block-size:3.2rem;margin-inline-end:.4rem;appearance:none;border:none;border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font-family:inherit;font-size:1.8rem;line-height:1;opacity:0}.et-query-devtools-row-pin:focus-visible,.et-query-devtools-row-pin--active{opacity:1}@media(hover:hover){.et-query-devtools-row:hover .et-query-devtools-row-pin{opacity:1}}@media(hover:none){.et-query-devtools-row-pin{opacity:1}}@media(hover:hover){.et-query-devtools-row-pin:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-row-pin--active,.et-query-devtools-row-pin--active:hover{color:var(--_et-qdt-accent)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row--gone{color:var(--_et-qdt-muted)}.et-query-devtools-row-caret{flex:0 0 auto;inline-size:1rem;color:var(--_et-qdt-subtle);font-size:1rem;line-height:1}.et-query-devtools-row-caret-btn{display:flex;flex:0 0 auto;align-items:center;appearance:none;border:none;background:transparent;color:inherit;cursor:pointer;padding-block:0;padding-inline:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem) 0;font:inherit}@media(hover:hover){.et-query-devtools-row-caret-btn:hover .et-query-devtools-row-caret{color:var(--_et-qdt-fg)}}.et-query-devtools-row>.et-query-devtools-row-select{padding-inline-start:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--nested>.et-query-devtools-row-select{padding-inline-start:calc(2.6rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--group>.et-query-devtools-row-select{padding-inline:.8rem 4.4rem}.et-query-devtools-consumer{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--folder{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-folder{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--group{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-row--group.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row-count{flex:0 0 auto;color:var(--_et-qdt-subtle);font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-row-time{flex:0 0 auto;inline-size:8ch;text-align:end;color:var(--_et-qdt-subtle);font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-sort{flex:0 0 auto;color:var(--_et-qdt-muted)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: QueryDevtoolsDetailComponent, selector: "et-query-devtools-detail", inputs: ["sel"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
4004
4454
  }
4005
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsQueriesTabComponent, decorators: [{
4455
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsQueriesTabComponent, decorators: [{
4006
4456
  type: Component,
4007
4457
  args: [{ selector: 'et-query-devtools-queries-tab', encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet, QueryDevtoolsDetailComponent, QueryDevtoolsRouteComponent], template: "<div #panes class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!host.inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n <!-- Search leads: it is what the list is narrowed with most of the time. The scope controls sit\n after it, and the count reads as the search's result rather than as one more control. -->\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #queryFilterInput\n [value]=\"host.queryFilter()\"\n (input)=\"host.queryFilter.set(queryFilterInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter method / route / url\u2026\"\n aria-label=\"Filter queries\"\n />\n @if (host.inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n <button\n (click)=\"host.clearInspectFilter()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the inspection filter and show all queries\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Clear\n </button>\n } @else {\n <select\n #clientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Client\"\n >\n <option value=\"\">All clients</option>\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n }\n <span class=\"et-query-devtools-count\">\n @if (isQueryListNarrowed()) {\n {{ filteredQueries().length }} of {{ scopedQueryCount() }}\n } @else {\n {{ scopedQueryCount() }}\n }\n </span>\n <button\n [disabled]=\"!filteredQueries().length\"\n (click)=\"downloadInsomniaCollection()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download the listed queries as an Insomnia collection, with the auth provider's token refresh\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Insomnia\n </button>\n </div>\n\n <div class=\"et-query-devtools-toolbar-row\">\n @let recentFirst = host.queryRecentFirst();\n <div class=\"et-query-devtools-btn-group\">\n <button\n [title]=\"\n (recentFirst ? 'Newest run first.' : 'Oldest run first.') +\n ' Click to reverse. Queries that have never run stay at the bottom either way.'\n \"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n recent <span aria-hidden=\"true\">{{ recentFirst ? '\u2193' : '\u2191' }}</span>\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n [title]=\"\n treeView\n ? 'Grouped by route path. Click for the flat list.'\n : 'Flat list. Click to group by route path - segments nothing branches off are folded into one row.'\n \"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2442</span> tree\n </button>\n </div>\n <!-- Only the statuses something is actually in. Five chips, four of them reading `0`, were four\n controls that could not be pressed taking up the row that says what is wrong. -->\n @for (facet of visibleFacets(); track facet.id) {\n <button\n [class.et-query-devtools-facet--active]=\"facet.active\"\n [attr.aria-pressed]=\"facet.active\"\n (click)=\"host.toggleFacet(facet.id)\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n >\n {{ facet.label }}\n <span class=\"et-query-devtools-facet-count\">{{ facet.count }}</span>\n </button>\n }\n @let goneListed = listedGoneQueries().length;\n @if (goneListed) {\n <button\n (click)=\"forgetGoneQueries()\"\n class=\"et-query-devtools-forget\"\n type=\"button\"\n title=\"Drop the {{ goneListed }} destroyed queries listed below - they cannot be brought back\"\n >\n Forget {{ goneListed }}\n </button>\n }\n @if (isQueryListNarrowed()) {\n <button\n (click)=\"host.clearQueryFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the search term and the status chips\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n </div>\n </div>\n\n @if (host.queryTreeView()) {\n @for (row of queryTree(); track row.key) {\n @if (row.kind === 'folder') {\n <div [style.--_et-qdt-tree-depth]=\"row.depth\" class=\"et-query-devtools-row et-query-devtools-row--folder\">\n <button\n [attr.aria-expanded]=\"!row.collapsed\"\n [title]=\"\n (row.collapsed ? 'Expand' : 'Collapse') + ' /' + row.label + ' - ' + row.count + ' queries below it'\n \"\n (click)=\"host.toggleQueryPath(row.key)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ row.collapsed ? '\u25B8' : '\u25BE' }}</span>\n <span class=\"et-query-devtools-folder\">/{{ row.label }}</span>\n <span class=\"et-query-devtools-row-count\">{{ row.count }}</span>\n </button>\n </div>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"queryGroupBlock\"\n [ngTemplateOutletContext]=\"{ group: row.item, depth: row.depth, trim: row.parentPath }\"\n />\n }\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n } @else {\n @for (group of queryGroups(); track group.key) {\n <ng-container [ngTemplateOutlet]=\"queryGroupBlock\" [ngTemplateOutletContext]=\"{ group, depth: 0, trim: '' }\" />\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n }\n\n <!-- An empty scope is a different problem from a filter that matches nothing, and only the\n latter is the user's own doing - so it names the way out. -->\n <ng-template #emptyList>\n @if (scopedQueryCount()) {\n <p class=\"et-query-devtools-empty\">No query matches the filter \u00B7 {{ scopedQueryCount() }} in scope.</p>\n } @else if (goneQueryCount()) {\n <p class=\"et-query-devtools-empty\">\n No query in scope still exists \u00B7 pick Gone to list the {{ goneQueryCount() }} destroyed.\n </p>\n } @else if (host.inspectFilterIds()) {\n <p class=\"et-query-devtools-empty\">\n The inspected element's queries are no longer registered. Clear the filter.\n </p>\n } @else {\n <p class=\"et-query-devtools-empty\">No queries registered.</p>\n }\n </ng-template>\n\n <ng-template #queryGroupBlock let-group=\"group\" let-depth=\"depth\" let-trim=\"trim\">\n @if (batchEntryOf(group); as batchEntry) {\n <ng-container\n [ngTemplateOutlet]=\"batchGroupBlock\"\n [ngTemplateOutletContext]=\"{ group, batchEntry, depth, trim }\"\n />\n } @else if (group.items.length === 1) {\n <ng-container [ngTemplateOutlet]=\"queryRow\" [ngTemplateOutletContext]=\"{ item: group.head, depth, trim }\" />\n } @else {\n @let expanded = isGroupExpanded(group);\n @let head = group.head;\n @let groupGone = !!head.entry.destroyedAt;\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <!-- The caret is its own control: every row in the fold is the same request, so opening the\n fold and inspecting what it holds are two different intentions. -->\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n [title]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"Inspect this request - all {{\n group.items.length\n }} consumers share it, so any of them answers for it\"\n >\n <span\n [attr.data-status]=\"groupGone ? null : groupStatus(group)\"\n [title]=\"groupGone ? 'destroyed' : groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ head.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(head.entry, head.query)\" [trimPrefix]=\"trim\" />\n </span>\n @if (groupGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"These queries no longer exist - what you see is the last state they held, frozen\"\n >\n gone\n </span>\n }\n @if (!groupGone && isGroupStale(group)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (isGroupTampered(group)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"At least one of these is showing an armed response override or a devtools-injected failure\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Consumers of this one request\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, ordinal: $index + 1, depth, trim }\"\n />\n }\n }\n }\n </ng-template>\n\n <!-- A bulk run's items, folded under the run rather than under what a row shows. They only exist\n while in flight - the batch destroys each item's query as it settles - so this row stands for\n the whole run, and the Batches tab is where its settled items are. -->\n <ng-template #batchGroupBlock let-group=\"group\" let-batchEntry=\"batchEntry\" let-depth=\"depth\" let-trim=\"trim\">\n @let batch = host.asBatch(batchEntry);\n @let expanded = isGroupExpanded(group);\n @let toggleLabel =\n (expanded ? 'Collapse' : 'Expand') + ' the ' + group.items.length + ' requests this batch has in flight';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"toggleLabel\"\n [title]=\"toggleLabel\"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"One item of a batch run - {{ batch.completed() }} of {{\n batch.total()\n }} settled. The Batches tab has the whole run.\"\n >\n <span\n [attr.data-status]=\"groupStatus(group)\"\n [title]=\"groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ batchEntry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(batchEntry)\" [trimPrefix]=\"trim\" />\n </span>\n <span\n class=\"et-query-devtools-chip\"\n title=\"Registered by createQueryBatch - inspect the run on the Batches tab\"\n >\n batch {{ batch.completed() }}/{{ batch.total() }}\n </span>\n @if (batch.failed()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">{{ batch.failed() }} failed</span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Requests this batch has in flight\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(group.head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, showRoute: true, depth, trim }\"\n />\n }\n }\n </ng-template>\n\n <ng-template\n #queryRow\n let-item=\"item\"\n let-nested=\"nested\"\n let-showRoute=\"showRoute\"\n let-ordinal=\"ordinal\"\n let-depth=\"depth\"\n let-trim=\"trim\"\n >\n @let isGone = !!item.entry.destroyedAt;\n @let isPinned = host.isQueryPinned(item.entry);\n @let pinLabel = isPinned ? 'Unpin this query' : 'Pin this query to the top of the list';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"host.selectedQueryId() === item.entry.id\"\n [class.et-query-devtools-row--gone]=\"isGone\"\n [class.et-query-devtools-row--nested]=\"nested\"\n class=\"et-query-devtools-row\"\n >\n <button (click)=\"host.selectedQueryId.set(item.entry.id)\" class=\"et-query-devtools-row-select\" type=\"button\">\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\"></span>\n <span\n [attr.data-status]=\"isGone ? null : host.queryStatus(item.query)\"\n [title]=\"isGone ? 'destroyed' : host.queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n <!-- Inside a fold the route is the fold's own head, repeated once per consumer. Where the query\n was created is the only thing that differs, so that is what the row says instead. A batch\n fold is the exception: its items differ in the route, which is why `showRoute` is set. -->\n @if (nested && !showRoute) {\n <span class=\"et-query-devtools-consumer\">\n @if (consumerLabel(item); as consumer) {\n &lt;{{ consumer }}&gt;\n } @else {\n created outside a component\n }\n </span>\n <span class=\"et-query-devtools-row-count\">#{{ ordinal }}</span>\n } @else {\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(item.entry, item.query)\" [trimPrefix]=\"trim\" />\n </span>\n }\n @if (isGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"This query no longer exists - what you see is the last state it held, frozen\"\n >\n gone\n </span>\n }\n <!-- A retry is the one thing a loading dot actively misrepresents: it reads as a request\n on its way when nothing is in flight at all. -->\n @if (!isGone && host.requestProgress(item.query)?.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Waiting out a retry backoff before attempt {{ retry.attempt }}\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ retry.attempt }}\n </span>\n }\n @if (!isGone && host.isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (host.isTampered(item.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(item)) }}</span>\n </button>\n <button\n [class.et-query-devtools-row-pin--active]=\"isPinned\"\n [title]=\"pinLabel\"\n [attr.aria-label]=\"pinLabel\"\n [attr.aria-pressed]=\"isPinned\"\n (click)=\"host.toggleQueryPin(item.entry)\"\n class=\"et-query-devtools-row-pin\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">{{ isPinned ? '\u2605' : '\u2606' }}</span>\n </button>\n </div>\n </ng-template>\n </div>\n\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'list', container: panes })\"\n (dblclick)=\"host.resetPaneSize('list')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <div class=\"et-query-devtools-detail\">\n @if (host.selectedQuery(); as sel) {\n <et-query-devtools-detail [sel]=\"sel\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 var(--_et-qdt-list-width, 34%);min-inline-size:var(--_et-qdt-list-width, 22rem);max-inline-size:var(--_et-qdt-list-width, 40rem);overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-forget{appearance:none;display:inline-flex;align-items:center;gap:.5rem;min-block-size:var(--_et-qdt-control-h);border:1px solid color-mix(in srgb,var(--_et-qdt-error) 50%,transparent);border-radius:var(--_et-qdt-radius);background:color-mix(in srgb,var(--_et-qdt-error) 10%,transparent);color:var(--_et-qdt-error);cursor:pointer;padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-forget:hover{border-color:var(--_et-qdt-error);background:color-mix(in srgb,var(--_et-qdt-error) 22%,transparent)}}.et-query-devtools-list .et-query-devtools-toolbar-row .et-query-devtools-search{flex:1 1 100%}.et-query-devtools-toolbar--filtered{background:linear-gradient(to right,color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent),transparent 65%),var(--_et-qdt-bg);box-shadow:inset .2rem 0 0 var(--_et-qdt-accent),inset 0 -1px color-mix(in srgb,var(--_et-qdt-accent) 30%,transparent)}.et-query-devtools-row-select{display:flex;flex:1 1 auto;align-items:center;gap:.8rem;min-inline-size:0;text-align:start;appearance:none;border:none;background:transparent;color:inherit;padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row-select>.et-query-devtools-chip{padding-block:.1rem;line-height:1.2}.et-query-devtools-row-pin{display:flex;flex:0 0 auto;align-self:center;align-items:center;justify-content:center;inline-size:3.2rem;block-size:3.2rem;margin-inline-end:.4rem;appearance:none;border:none;border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font-family:inherit;font-size:1.8rem;line-height:1;opacity:0}.et-query-devtools-row-pin:focus-visible,.et-query-devtools-row-pin--active{opacity:1}@media(hover:hover){.et-query-devtools-row:hover .et-query-devtools-row-pin{opacity:1}}@media(hover:none){.et-query-devtools-row-pin{opacity:1}}@media(hover:hover){.et-query-devtools-row-pin:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-row-pin--active,.et-query-devtools-row-pin--active:hover{color:var(--_et-qdt-accent)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row--gone{color:var(--_et-qdt-muted)}.et-query-devtools-row-caret{flex:0 0 auto;inline-size:1rem;color:var(--_et-qdt-subtle);font-size:1rem;line-height:1}.et-query-devtools-row-caret-btn{display:flex;flex:0 0 auto;align-items:center;appearance:none;border:none;background:transparent;color:inherit;cursor:pointer;padding-block:0;padding-inline:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem) 0;font:inherit}@media(hover:hover){.et-query-devtools-row-caret-btn:hover .et-query-devtools-row-caret{color:var(--_et-qdt-fg)}}.et-query-devtools-row>.et-query-devtools-row-select{padding-inline-start:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--nested>.et-query-devtools-row-select{padding-inline-start:calc(2.6rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--group>.et-query-devtools-row-select{padding-inline:.8rem 4.4rem}.et-query-devtools-consumer{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--folder{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-folder{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--group{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-row--group.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row-count{flex:0 0 auto;color:var(--_et-qdt-subtle);font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-row-time{flex:0 0 auto;inline-size:8ch;text-align:end;color:var(--_et-qdt-subtle);font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-sort{flex:0 0 auto;color:var(--_et-qdt-muted)}}\n"] }]
4008
4458
  }], ctorParameters: () => [] });
@@ -4015,10 +4465,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
4015
4465
  * @internal
4016
4466
  */
4017
4467
  class QueryDevtoolsStepStylesComponent {
4018
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsStepStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4019
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.7", type: QueryDevtoolsStepStylesComponent, isStandalone: true, selector: "et-query-devtools-step-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{.et-query-devtools-step-list{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none}.et-query-devtools-step-item{border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);overflow:hidden}.et-query-devtools-step-item[data-status=error]{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-step-head{display:flex;align-items:stretch}.et-query-devtools-step-head .et-query-devtools-inner-row{border:none;border-radius:0;flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret-btn{appearance:none;border:none;border-inline-start:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:0 .7rem;font:inherit}@media(hover:hover){.et-query-devtools-step-caret-btn:hover{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}}.et-query-devtools-step-io{padding:0 .8rem .8rem;border-block-start:1px solid var(--_et-qdt-border)}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
4468
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsStepStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4469
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: QueryDevtoolsStepStylesComponent, isStandalone: true, selector: "et-query-devtools-step-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{.et-query-devtools-step-list{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none}.et-query-devtools-step-item{border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);overflow:hidden}.et-query-devtools-step-item[data-status=error]{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-step-head{display:flex;align-items:stretch}.et-query-devtools-step-head .et-query-devtools-inner-row{border:none;border-radius:0;flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret-btn{appearance:none;border:none;border-inline-start:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:0 .7rem;font:inherit}@media(hover:hover){.et-query-devtools-step-caret-btn:hover{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}}.et-query-devtools-step-io{padding:0 .8rem .8rem;border-block-start:1px solid var(--_et-qdt-border)}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
4020
4470
  }
4021
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsStepStylesComponent, decorators: [{
4471
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsStepStylesComponent, decorators: [{
4022
4472
  type: Component,
4023
4473
  args: [{ selector: 'et-query-devtools-step-styles', template: '', encapsulation: ViewEncapsulation.None, styles: ["@layer components{.et-query-devtools-step-list{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none}.et-query-devtools-step-item{border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);overflow:hidden}.et-query-devtools-step-item[data-status=error]{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-step-head{display:flex;align-items:stretch}.et-query-devtools-step-head .et-query-devtools-inner-row{border:none;border-radius:0;flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret-btn{appearance:none;border:none;border-inline-start:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:0 .7rem;font:inherit}@media(hover:hover){.et-query-devtools-step-caret-btn:hover{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}}.et-query-devtools-step-io{padding:0 .8rem .8rem;border-block-start:1px solid var(--_et-qdt-border)}}\n"] }]
4024
4474
  }] });
@@ -4052,10 +4502,10 @@ class QueryDevtoolsBatchesTabComponent {
4052
4502
  return null;
4053
4503
  return { status: result.error.raw.status, body: result.error.isList ? result.error.errors : result.error.error };
4054
4504
  }
4055
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsBatchesTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4056
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsBatchesTabComponent, isStandalone: true, selector: "et-query-devtools-batches-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.batchEntries(); track entry.id) {\n @let batch = host.asBatch(entry);\n @if (batch) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">batch</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ batch.status() }}</span>\n <span class=\"et-query-devtools-method\">{{ entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(entry)\" fallback=\"no route\" />\n </span>\n <span class=\"et-query-devtools-count\">{{ batch.completed() }} / {{ batch.total() }}</span>\n </div>\n\n <div class=\"et-query-devtools-batch-bar\" role=\"presentation\">\n <!-- Coloured by the run, not by whether an item failed: a bar filled red end to end reads as\n \"nothing landed\", which is the opposite of what a partial run means. -->\n <span\n [style.inline-size.%]=\"batch.progress()\"\n [attr.data-status]=\"batch.status()\"\n class=\"et-query-devtools-batch-bar-fill\"\n ></span>\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Outcomes</dt>\n <dd>\n @if (batch.total()) {\n {{ batch.succeeded() }} ok\n @if (batch.failed()) {\n \u00B7 <span class=\"et-query-devtools-batch-failed\">{{ batch.failed() }} failed</span>\n }\n @if (batch.skipped()) {\n \u00B7 {{ batch.skipped() }} skipped\n }\n @let notAttempted = batch.total() - batch.completed();\n @if (!batch.running() && notAttempted) {\n \u00B7 {{ notAttempted }} not attempted\n }\n } @else {\n \u2014 never run\n }\n </dd>\n\n <dt>Throughput</dt>\n <dd>\n @if (host.batchThroughput(batch); as throughput) {\n {{ throughput }}\n } @else if (batch.running()) {\n measuring\u2026\n } @else if (batch.total()) {\n <span title=\"A rate needs the first `concurrency` items and a couple of seconds of run time\">\n run too short to measure\n </span>\n } @else {\n \u2014\n }\n </dd>\n\n <dt>In flight</dt>\n <dd>\n {{ batch.inFlight() }} of {{ entry.meta.concurrency }} concurrent\n @if (entry.meta.stopOnError) {\n \u00B7 stops on the first failure\n }\n </dd>\n </dl>\n\n <!-- Only what is in flight: the batch destroys an item's query the moment it settles, so a\n settled item is in the list below instead of here. -->\n @let inFlight = host.queriesForBatch(entry);\n @if (inFlight.length) {\n <h5 class=\"et-query-devtools-section\">In flight</h5>\n <ul class=\"et-query-devtools-step-list\">\n @for (link of inFlight; track link.id) {\n <li>\n <button\n [class.et-query-devtools-inner-row--active]=\"host.batchSelectedQueryId() === link.id\"\n (click)=\"host.batchSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-status\" data-status=\"loading\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" fallback=\"in flight\" />\n </span>\n </button>\n </li>\n }\n </ul>\n }\n\n @let items = host.batchItems(entry);\n @if (items.rows.length) {\n <h5 class=\"et-query-devtools-section\">\n Items\n @if (items.hidden) {\n <span class=\"et-query-devtools-count\" title=\"Failures are listed first, so the cap never drops one\">\n {{ items.rows.length }} of {{ items.total }} \u00B7 {{ items.hidden }} not listed\n </span>\n } @else {\n <span class=\"et-query-devtools-count\">{{ items.total }}</span>\n }\n </h5>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (row of items.rows; track row.result.index) {\n @let result = row.result;\n @let expanded = host.isBatchItemExpanded(entry.id, result.index);\n @let itemQueryId = host.batchItemQueryId(entry, result.index);\n <li [attr.data-status]=\"itemStatus(result)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <!-- The row opens the item's own query in the drawer, the way a sequence step does.\n Disabled once that query has fallen out of the batch's capped tombstone tail -\n the caret below still has this item's args and outcome either way. -->\n <button\n [class.et-query-devtools-inner-row--active]=\"\n !!itemQueryId && host.batchSelectedQueryId() === itemQueryId\n \"\n [disabled]=\"!itemQueryId\"\n [title]=\"\n itemQueryId\n ? 'Inspect this item\\'s request'\n : 'This item\\'s query is no longer kept - open the row for its args and outcome'\n \"\n (click)=\"host.batchSelectedQueryId.set(itemQueryId)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"itemStatus(result)\"\n [title]=\"result.status\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">#{{ result.index }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"row.segments\" fallback=\"not sent\" />\n </span>\n <!-- The dot already says success or error; only an item that never went out needs\n a word, since a neutral dot alone cannot tell the two reasons apart. -->\n @if (!wasSent(result)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ result.status }}</span>\n }\n </button>\n <button\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.toggleBatchItem(entry.id, result.index)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ expanded ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (expanded) {\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">Item</h5>\n <et-query-devtools-json [value]=\"result.item\" />\n\n @if (wasSent(result)) {\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n <et-query-devtools-json [value]=\"itemArgs(result)\" />\n\n @if (itemError(result); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.status }})</h5>\n <et-query-devtools-json [value]=\"error.body\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n <et-query-devtools-json [value]=\"itemResponse(result)\" />\n }\n } @else {\n <p class=\"et-query-devtools-empty\">\n @if (result.status === 'skipped') {\n The args function returned null, so nothing was sent.\n } @else {\n Still queued when the run stopped - nothing was sent.\n }\n </p>\n }\n </div>\n }\n </li>\n }\n </ol>\n }\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No batches registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.batchSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.batchSelectedQueryId.set(null)\"\n />\n</div>\n", styles: ["@layer components{.et-query-devtools-batch-bar{block-size:.6rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-interaction) 10%,transparent);overflow:hidden;margin-block-end:1rem}.et-query-devtools-batch-bar-fill{display:block;block-size:100%;background:var(--_et-qdt-accent);transition:inline-size .15s linear}.et-query-devtools-batch-bar-fill[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-batch-bar-fill[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-batch-failed{color:var(--_et-qdt-error)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
4505
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsBatchesTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4506
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsBatchesTabComponent, isStandalone: true, selector: "et-query-devtools-batches-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.batchEntries(); track entry.id) {\n @let batch = host.asBatch(entry);\n @if (batch) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">batch</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ batch.status() }}</span>\n <span class=\"et-query-devtools-method\">{{ entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(entry)\" fallback=\"no route\" />\n </span>\n <span class=\"et-query-devtools-count\">{{ batch.completed() }} / {{ batch.total() }}</span>\n </div>\n\n <div class=\"et-query-devtools-batch-bar\" role=\"presentation\">\n <!-- Coloured by the run, not by whether an item failed: a bar filled red end to end reads as\n \"nothing landed\", which is the opposite of what a partial run means. -->\n <span\n [style.inline-size.%]=\"batch.progress()\"\n [attr.data-status]=\"batch.status()\"\n class=\"et-query-devtools-batch-bar-fill\"\n ></span>\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Outcomes</dt>\n <dd>\n @if (batch.total()) {\n {{ batch.succeeded() }} ok\n @if (batch.failed()) {\n \u00B7 <span class=\"et-query-devtools-batch-failed\">{{ batch.failed() }} failed</span>\n }\n @if (batch.skipped()) {\n \u00B7 {{ batch.skipped() }} skipped\n }\n @let notAttempted = batch.total() - batch.completed();\n @if (!batch.running() && notAttempted) {\n \u00B7 {{ notAttempted }} not attempted\n }\n } @else {\n \u2014 never run\n }\n </dd>\n\n <dt>Throughput</dt>\n <dd>\n @if (host.batchThroughput(batch); as throughput) {\n {{ throughput }}\n } @else if (batch.running()) {\n measuring\u2026\n } @else if (batch.total()) {\n <span title=\"A rate needs the first `concurrency` items and a couple of seconds of run time\">\n run too short to measure\n </span>\n } @else {\n \u2014\n }\n </dd>\n\n <dt>In flight</dt>\n <dd>\n {{ batch.inFlight() }} of {{ entry.meta.concurrency }} concurrent\n @if (entry.meta.stopOnError) {\n \u00B7 stops on the first failure\n }\n </dd>\n </dl>\n\n <!-- Only what is in flight: the batch destroys an item's query the moment it settles, so a\n settled item is in the list below instead of here. -->\n @let inFlight = host.queriesForBatch(entry);\n @if (inFlight.length) {\n <h5 class=\"et-query-devtools-section\">In flight</h5>\n <ul class=\"et-query-devtools-step-list\">\n @for (link of inFlight; track link.id) {\n <li>\n <button\n [class.et-query-devtools-inner-row--active]=\"host.batchSelectedQueryId() === link.id\"\n (click)=\"host.batchSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-status\" data-status=\"loading\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" fallback=\"in flight\" />\n </span>\n </button>\n </li>\n }\n </ul>\n }\n\n @let items = host.batchItems(entry);\n @if (items.rows.length) {\n <h5 class=\"et-query-devtools-section\">\n Items\n @if (items.hidden) {\n <span class=\"et-query-devtools-count\" title=\"Failures are listed first, so the cap never drops one\">\n {{ items.rows.length }} of {{ items.total }} \u00B7 {{ items.hidden }} not listed\n </span>\n } @else {\n <span class=\"et-query-devtools-count\">{{ items.total }}</span>\n }\n </h5>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (row of items.rows; track row.result.index) {\n @let result = row.result;\n @let expanded = host.isBatchItemExpanded(entry.id, result.index);\n @let itemQueryId = host.batchItemQueryId(entry, result.index);\n <li [attr.data-status]=\"itemStatus(result)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <!-- The row opens the item's own query in the drawer, the way a sequence step does.\n Disabled once that query has fallen out of the batch's capped tombstone tail -\n the caret below still has this item's args and outcome either way. -->\n <button\n [class.et-query-devtools-inner-row--active]=\"\n !!itemQueryId && host.batchSelectedQueryId() === itemQueryId\n \"\n [disabled]=\"!itemQueryId\"\n [title]=\"\n itemQueryId\n ? 'Inspect this item\\'s request'\n : 'This item\\'s query is no longer kept - open the row for its args and outcome'\n \"\n (click)=\"host.batchSelectedQueryId.set(itemQueryId)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"itemStatus(result)\"\n [title]=\"result.status\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">#{{ result.index }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"row.segments\" fallback=\"not sent\" />\n </span>\n <!-- The dot already says success or error; only an item that never went out needs\n a word, since a neutral dot alone cannot tell the two reasons apart. -->\n @if (!wasSent(result)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ result.status }}</span>\n }\n </button>\n <button\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.toggleBatchItem(entry.id, result.index)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ expanded ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (expanded) {\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">Item</h5>\n <et-query-devtools-json [value]=\"result.item\" />\n\n @if (wasSent(result)) {\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n <et-query-devtools-json [value]=\"itemArgs(result)\" />\n\n @if (itemError(result); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.status }})</h5>\n <et-query-devtools-json [value]=\"error.body\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n <et-query-devtools-json [value]=\"itemResponse(result)\" />\n }\n } @else {\n <p class=\"et-query-devtools-empty\">\n @if (result.status === 'skipped') {\n The args function returned null, so nothing was sent.\n } @else {\n Still queued when the run stopped - nothing was sent.\n }\n </p>\n }\n </div>\n }\n </li>\n }\n </ol>\n }\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No batches registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.batchSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.batchSelectedQueryId.set(null)\"\n />\n</div>\n", styles: ["@layer components{.et-query-devtools-batch-bar{block-size:.6rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-interaction) 10%,transparent);overflow:hidden;margin-block-end:1rem}.et-query-devtools-batch-bar-fill{display:block;block-size:100%;background:var(--_et-qdt-accent);transition:inline-size .15s linear}.et-query-devtools-batch-bar-fill[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-batch-bar-fill[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-batch-failed{color:var(--_et-qdt-error)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
4057
4507
  }
4058
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsBatchesTabComponent, decorators: [{
4508
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsBatchesTabComponent, decorators: [{
4059
4509
  type: Component,
4060
4510
  args: [{ selector: 'et-query-devtools-batches-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDrawerComponent, QueryDevtoolsJsonComponent, QueryDevtoolsRouteComponent], template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.batchEntries(); track entry.id) {\n @let batch = host.asBatch(entry);\n @if (batch) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">batch</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ batch.status() }}</span>\n <span class=\"et-query-devtools-method\">{{ entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(entry)\" fallback=\"no route\" />\n </span>\n <span class=\"et-query-devtools-count\">{{ batch.completed() }} / {{ batch.total() }}</span>\n </div>\n\n <div class=\"et-query-devtools-batch-bar\" role=\"presentation\">\n <!-- Coloured by the run, not by whether an item failed: a bar filled red end to end reads as\n \"nothing landed\", which is the opposite of what a partial run means. -->\n <span\n [style.inline-size.%]=\"batch.progress()\"\n [attr.data-status]=\"batch.status()\"\n class=\"et-query-devtools-batch-bar-fill\"\n ></span>\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Outcomes</dt>\n <dd>\n @if (batch.total()) {\n {{ batch.succeeded() }} ok\n @if (batch.failed()) {\n \u00B7 <span class=\"et-query-devtools-batch-failed\">{{ batch.failed() }} failed</span>\n }\n @if (batch.skipped()) {\n \u00B7 {{ batch.skipped() }} skipped\n }\n @let notAttempted = batch.total() - batch.completed();\n @if (!batch.running() && notAttempted) {\n \u00B7 {{ notAttempted }} not attempted\n }\n } @else {\n \u2014 never run\n }\n </dd>\n\n <dt>Throughput</dt>\n <dd>\n @if (host.batchThroughput(batch); as throughput) {\n {{ throughput }}\n } @else if (batch.running()) {\n measuring\u2026\n } @else if (batch.total()) {\n <span title=\"A rate needs the first `concurrency` items and a couple of seconds of run time\">\n run too short to measure\n </span>\n } @else {\n \u2014\n }\n </dd>\n\n <dt>In flight</dt>\n <dd>\n {{ batch.inFlight() }} of {{ entry.meta.concurrency }} concurrent\n @if (entry.meta.stopOnError) {\n \u00B7 stops on the first failure\n }\n </dd>\n </dl>\n\n <!-- Only what is in flight: the batch destroys an item's query the moment it settles, so a\n settled item is in the list below instead of here. -->\n @let inFlight = host.queriesForBatch(entry);\n @if (inFlight.length) {\n <h5 class=\"et-query-devtools-section\">In flight</h5>\n <ul class=\"et-query-devtools-step-list\">\n @for (link of inFlight; track link.id) {\n <li>\n <button\n [class.et-query-devtools-inner-row--active]=\"host.batchSelectedQueryId() === link.id\"\n (click)=\"host.batchSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-status\" data-status=\"loading\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" fallback=\"in flight\" />\n </span>\n </button>\n </li>\n }\n </ul>\n }\n\n @let items = host.batchItems(entry);\n @if (items.rows.length) {\n <h5 class=\"et-query-devtools-section\">\n Items\n @if (items.hidden) {\n <span class=\"et-query-devtools-count\" title=\"Failures are listed first, so the cap never drops one\">\n {{ items.rows.length }} of {{ items.total }} \u00B7 {{ items.hidden }} not listed\n </span>\n } @else {\n <span class=\"et-query-devtools-count\">{{ items.total }}</span>\n }\n </h5>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (row of items.rows; track row.result.index) {\n @let result = row.result;\n @let expanded = host.isBatchItemExpanded(entry.id, result.index);\n @let itemQueryId = host.batchItemQueryId(entry, result.index);\n <li [attr.data-status]=\"itemStatus(result)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <!-- The row opens the item's own query in the drawer, the way a sequence step does.\n Disabled once that query has fallen out of the batch's capped tombstone tail -\n the caret below still has this item's args and outcome either way. -->\n <button\n [class.et-query-devtools-inner-row--active]=\"\n !!itemQueryId && host.batchSelectedQueryId() === itemQueryId\n \"\n [disabled]=\"!itemQueryId\"\n [title]=\"\n itemQueryId\n ? 'Inspect this item\\'s request'\n : 'This item\\'s query is no longer kept - open the row for its args and outcome'\n \"\n (click)=\"host.batchSelectedQueryId.set(itemQueryId)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"itemStatus(result)\"\n [title]=\"result.status\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">#{{ result.index }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"row.segments\" fallback=\"not sent\" />\n </span>\n <!-- The dot already says success or error; only an item that never went out needs\n a word, since a neutral dot alone cannot tell the two reasons apart. -->\n @if (!wasSent(result)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ result.status }}</span>\n }\n </button>\n <button\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.toggleBatchItem(entry.id, result.index)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ expanded ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (expanded) {\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">Item</h5>\n <et-query-devtools-json [value]=\"result.item\" />\n\n @if (wasSent(result)) {\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n <et-query-devtools-json [value]=\"itemArgs(result)\" />\n\n @if (itemError(result); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.status }})</h5>\n <et-query-devtools-json [value]=\"error.body\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n <et-query-devtools-json [value]=\"itemResponse(result)\" />\n }\n } @else {\n <p class=\"et-query-devtools-empty\">\n @if (result.status === 'skipped') {\n The args function returned null, so nothing was sent.\n } @else {\n Still queued when the run stopped - nothing was sent.\n }\n </p>\n }\n </div>\n }\n </li>\n }\n </ol>\n }\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No batches registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.batchSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.batchSelectedQueryId.set(null)\"\n />\n</div>\n", styles: ["@layer components{.et-query-devtools-batch-bar{block-size:.6rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-interaction) 10%,transparent);overflow:hidden;margin-block-end:1rem}.et-query-devtools-batch-bar-fill{display:block;block-size:100%;background:var(--_et-qdt-accent);transition:inline-size .15s linear}.et-query-devtools-batch-bar-fill[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-batch-bar-fill[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-batch-failed{color:var(--_et-qdt-error)}}\n"] }]
4061
4511
  }], ctorParameters: () => [] });
@@ -4066,10 +4516,10 @@ class QueryDevtoolsSequencesTabComponent {
4066
4516
  this.host = injectQueryDevtoolsHost();
4067
4517
  injectStyleManager().mount(QueryDevtoolsStepStylesComponent);
4068
4518
  }
4069
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsSequencesTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4070
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsSequencesTabComponent, isStandalone: true, selector: "et-query-devtools-sequences-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.sequenceEntries(); track entry.id) {\n @let sequence = host.asSequence(entry);\n @if (sequence) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">sequence</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ sequence.status() }}</span>\n <span class=\"et-query-devtools-count\">step {{ sequence.currentStep() }} / {{ sequence.total }}</span>\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Traffic</dt>\n <dd>\n @let activity = host.sequenceActivity(sequence);\n @if (activity.hasActivity) {\n {{ activity.stats.requests }} request{{ activity.stats.requests === 1 ? '' : 's' }} \u00B7 \u2193\n {{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}\n @if (activity.stats.sentBytes) {\n \u00B7 \u2191 {{ host.formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}\n }\n @if (activity.cacheServed) {\n \u00B7 {{ activity.cacheServed }} from cache\n }\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n } @else {\n \u2014\n }\n </dd>\n </dl>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (link of host.queriesForSequence(sequence); track $index; let i = $index) {\n <li [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <button\n [class.et-query-devtools-inner-row--active]=\"host.sequenceSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"host.sequenceSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ i + 1 }}. {{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" [fallback]=\"'step ' + (i + 1)\" />\n </span>\n </button>\n <button\n [attr.aria-expanded]=\"host.isStepExpanded(entry.id, i)\"\n (click)=\"host.toggleStep(entry.id, i)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ host.isStepExpanded(entry.id, i) ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (host.isStepExpanded(entry.id, i)) {\n @let snap = host.stepSnapshot(sequence, i);\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n @if (sequence.stepArgs().length > i) {\n <et-query-devtools-json [value]=\"sequence.stepArgs()[i]\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n\n @if (snap?.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.raw.status }})</h5>\n <et-query-devtools-json [value]=\"error.isList ? error.errors : error.error\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n @if (snap) {\n <et-query-devtools-json [value]=\"snap.response()\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n }\n </div>\n }\n </li>\n }\n </ol>\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No sequences registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.sequenceSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.sequenceSelectedQueryId.set(null)\"\n />\n</div>\n", dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
4519
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsSequencesTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4520
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsSequencesTabComponent, isStandalone: true, selector: "et-query-devtools-sequences-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.sequenceEntries(); track entry.id) {\n @let sequence = host.asSequence(entry);\n @if (sequence) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">sequence</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ sequence.status() }}</span>\n <span class=\"et-query-devtools-count\">step {{ sequence.currentStep() }} / {{ sequence.total }}</span>\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Traffic</dt>\n <dd>\n @let activity = host.sequenceActivity(sequence);\n @if (activity.hasActivity) {\n {{ activity.stats.requests }} request{{ activity.stats.requests === 1 ? '' : 's' }} \u00B7 \u2193\n {{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}\n @if (activity.stats.sentBytes) {\n \u00B7 \u2191 {{ host.formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}\n }\n @if (activity.cacheServed) {\n \u00B7 {{ activity.cacheServed }} from cache\n }\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n } @else {\n \u2014\n }\n </dd>\n </dl>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (link of host.queriesForSequence(sequence); track $index; let i = $index) {\n <li [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <button\n [class.et-query-devtools-inner-row--active]=\"host.sequenceSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"host.sequenceSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ i + 1 }}. {{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" [fallback]=\"'step ' + (i + 1)\" />\n </span>\n </button>\n <button\n [attr.aria-expanded]=\"host.isStepExpanded(entry.id, i)\"\n (click)=\"host.toggleStep(entry.id, i)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ host.isStepExpanded(entry.id, i) ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (host.isStepExpanded(entry.id, i)) {\n @let snap = host.stepSnapshot(sequence, i);\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n @if (sequence.stepArgs().length > i) {\n <et-query-devtools-json [value]=\"sequence.stepArgs()[i]\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n\n @if (snap?.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.raw.status }})</h5>\n <et-query-devtools-json [value]=\"error.isList ? error.errors : error.error\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n @if (snap) {\n <et-query-devtools-json [value]=\"snap.response()\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n }\n </div>\n }\n </li>\n }\n </ol>\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No sequences registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.sequenceSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.sequenceSelectedQueryId.set(null)\"\n />\n</div>\n", dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
4071
4521
  }
4072
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsSequencesTabComponent, decorators: [{
4522
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsSequencesTabComponent, decorators: [{
4073
4523
  type: Component,
4074
4524
  args: [{ selector: 'et-query-devtools-sequences-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDrawerComponent, QueryDevtoolsJsonComponent, QueryDevtoolsRouteComponent], template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.sequenceEntries(); track entry.id) {\n @let sequence = host.asSequence(entry);\n @if (sequence) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">sequence</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ sequence.status() }}</span>\n <span class=\"et-query-devtools-count\">step {{ sequence.currentStep() }} / {{ sequence.total }}</span>\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Traffic</dt>\n <dd>\n @let activity = host.sequenceActivity(sequence);\n @if (activity.hasActivity) {\n {{ activity.stats.requests }} request{{ activity.stats.requests === 1 ? '' : 's' }} \u00B7 \u2193\n {{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}\n @if (activity.stats.sentBytes) {\n \u00B7 \u2191 {{ host.formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}\n }\n @if (activity.cacheServed) {\n \u00B7 {{ activity.cacheServed }} from cache\n }\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n } @else {\n \u2014\n }\n </dd>\n </dl>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (link of host.queriesForSequence(sequence); track $index; let i = $index) {\n <li [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <button\n [class.et-query-devtools-inner-row--active]=\"host.sequenceSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"host.sequenceSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ i + 1 }}. {{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" [fallback]=\"'step ' + (i + 1)\" />\n </span>\n </button>\n <button\n [attr.aria-expanded]=\"host.isStepExpanded(entry.id, i)\"\n (click)=\"host.toggleStep(entry.id, i)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ host.isStepExpanded(entry.id, i) ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (host.isStepExpanded(entry.id, i)) {\n @let snap = host.stepSnapshot(sequence, i);\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n @if (sequence.stepArgs().length > i) {\n <et-query-devtools-json [value]=\"sequence.stepArgs()[i]\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n\n @if (snap?.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.raw.status }})</h5>\n <et-query-devtools-json [value]=\"error.isList ? error.errors : error.error\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n @if (snap) {\n <et-query-devtools-json [value]=\"snap.response()\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n }\n </div>\n }\n </li>\n }\n </ol>\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No sequences registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.sequenceSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.sequenceSelectedQueryId.set(null)\"\n />\n</div>\n" }]
4075
4525
  }], ctorParameters: () => [] });
@@ -4079,10 +4529,10 @@ class QueryDevtoolsSocketsTabComponent {
4079
4529
  constructor() {
4080
4530
  this.host = injectQueryDevtoolsHost();
4081
4531
  }
4082
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsSocketsTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4083
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsSocketsTabComponent, isStandalone: true, selector: "et-query-devtools-sockets-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n @if (host.wsEntries().length) {\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #socketFilterInput\n [value]=\"host.socketFilter()\"\n (input)=\"host.socketFilter.set(socketFilterInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter event / room / direction\u2026\"\n aria-label=\"Filter socket messages\"\n />\n @if (host.socketFilter()) {\n <button\n (click)=\"host.socketFilter.set('')\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the message filter\"\n aria-label=\"Clear the message filter\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n </div>\n </div>\n }\n\n @for (entry of host.wsEntries(); track entry.id) {\n @let ws = host.asWs(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'socket' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"ws.connected()\"\n [class.et-query-devtools-chip--muted]=\"!ws.connected()\"\n class=\"et-query-devtools-chip\"\n >\n {{ ws.connected() ? 'connected' : 'disconnected' }}\n </span>\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.url }}</dd>\n <dt>Rooms</dt>\n <dd>\n @if (ws.rooms().length) {\n <div class=\"et-query-devtools-chips\">\n @for (room of ws.rooms(); track room) {\n <span class=\"et-query-devtools-chip\">{{ room }}</span>\n }\n </div>\n } @else {\n none\n }\n </dd>\n </dl>\n\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">Emit</h5>\n <span class=\"et-query-devtools-hint\">\n Sends as the app would, so a server that only answers a client that asked can be provoked from here.\n </span>\n </div>\n <div class=\"et-query-devtools-emit\">\n <input #emitEvent class=\"et-query-devtools-input\" type=\"text\" placeholder=\"event\" aria-label=\"Event to emit\" />\n <input\n #emitData\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"JSON payload (optional)\"\n aria-label=\"Payload to emit\"\n />\n <button\n (click)=\"host.emitSocketMessage({ entry, event: emitEvent.value, data: emitData.value })\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n \u2191 Send\n </button>\n </div>\n @if (host.socketEmitErrorFor(entry.id); as emitError) {\n <p class=\"et-query-devtools-editor-error\">{{ emitError }}</p>\n }\n\n @let messages = host.socketMessages(ws);\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">Messages</h5>\n <span class=\"et-query-devtools-count\">\n @if (host.socketFilter()) {\n {{ messages.length }} of {{ ws.messages().length }}\n } @else {\n {{ messages.length }}\n }\n </span>\n </div>\n @for (message of messages; track message.id) {\n <div [attr.data-direction]=\"message.direction\" class=\"et-query-devtools-ws-message\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-count\">{{ host.formatTime(message.timestamp) }}</span>\n <span class=\"et-query-devtools-ws-direction\">{{ host.socketDirectionLabel(message) }}</span>\n @if (message.room) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ message.room }}</span>\n }\n <span class=\"et-query-devtools-method\">{{ message.event }}</span>\n </div>\n @if (message.data !== undefined) {\n <et-query-devtools-json [value]=\"message.data\" />\n }\n </div>\n } @empty {\n @if (ws.messages().length) {\n <p class=\"et-query-devtools-empty\">No message matches the filter \u00B7 {{ ws.messages().length }} in the log.</p>\n } @else {\n <p class=\"et-query-devtools-empty\">Nothing sent or received yet.</p>\n }\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No web socket clients registered.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-ws-message{border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);padding:.6rem;margin-block-end:.4rem}.et-query-devtools-ws-message:where([data-direction=out]){border-inline-start:2px solid color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent)}.et-query-devtools-ws-direction{color:var(--_et-qdt-muted);white-space:nowrap}.et-query-devtools-ws-message:where([data-direction=out]) .et-query-devtools-ws-direction{color:var(--_et-qdt-accent)}.et-query-devtools-emit{display:flex;align-items:center;gap:.4rem;margin-block:.4rem}.et-query-devtools-emit .et-query-devtools-input{flex:0 0 14rem}.et-query-devtools-emit .et-query-devtools-input+.et-query-devtools-input{flex:1 1 auto}.et-query-devtools-emit .et-query-devtools-btn{flex:0 0 auto}}\n"], dependencies: [{ 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 }); }
4532
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsSocketsTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4533
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsSocketsTabComponent, isStandalone: true, selector: "et-query-devtools-sockets-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n @if (host.wsEntries().length) {\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #socketFilterInput\n [value]=\"host.socketFilter()\"\n (input)=\"host.socketFilter.set(socketFilterInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter event / room / direction\u2026\"\n aria-label=\"Filter socket messages\"\n />\n @if (host.socketFilter()) {\n <button\n (click)=\"host.socketFilter.set('')\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the message filter\"\n aria-label=\"Clear the message filter\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n </div>\n </div>\n }\n\n @for (entry of host.wsEntries(); track entry.id) {\n @let ws = host.asWs(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'socket' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"ws.connected()\"\n [class.et-query-devtools-chip--muted]=\"!ws.connected()\"\n class=\"et-query-devtools-chip\"\n >\n {{ ws.connected() ? 'connected' : 'disconnected' }}\n </span>\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.url }}</dd>\n <dt>Rooms</dt>\n <dd>\n @if (ws.rooms().length) {\n <div class=\"et-query-devtools-chips\">\n @for (room of ws.rooms(); track room) {\n <span class=\"et-query-devtools-chip\">{{ room }}</span>\n }\n </div>\n } @else {\n none\n }\n </dd>\n </dl>\n\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">Emit</h5>\n <span class=\"et-query-devtools-hint\">\n Sends as the app would, so a server that only answers a client that asked can be provoked from here.\n </span>\n </div>\n <div class=\"et-query-devtools-emit\">\n <input #emitEvent class=\"et-query-devtools-input\" type=\"text\" placeholder=\"event\" aria-label=\"Event to emit\" />\n <input\n #emitData\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"JSON payload (optional)\"\n aria-label=\"Payload to emit\"\n />\n <button\n (click)=\"host.emitSocketMessage({ entry, event: emitEvent.value, data: emitData.value })\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n \u2191 Send\n </button>\n </div>\n @if (host.socketEmitErrorFor(entry.id); as emitError) {\n <p class=\"et-query-devtools-editor-error\">{{ emitError }}</p>\n }\n\n @let messages = host.socketMessages(ws);\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">Messages</h5>\n <span class=\"et-query-devtools-count\">\n @if (host.socketFilter()) {\n {{ messages.length }} of {{ ws.messages().length }}\n } @else {\n {{ messages.length }}\n }\n </span>\n </div>\n @for (message of messages; track message.id) {\n <div [attr.data-direction]=\"message.direction\" class=\"et-query-devtools-ws-message\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-count\">{{ host.formatTime(message.timestamp) }}</span>\n <span class=\"et-query-devtools-ws-direction\">{{ host.socketDirectionLabel(message) }}</span>\n @if (message.room) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ message.room }}</span>\n }\n <span class=\"et-query-devtools-method\">{{ message.event }}</span>\n </div>\n @if (message.data !== undefined) {\n <et-query-devtools-json [value]=\"message.data\" />\n }\n </div>\n } @empty {\n @if (ws.messages().length) {\n <p class=\"et-query-devtools-empty\">No message matches the filter \u00B7 {{ ws.messages().length }} in the log.</p>\n } @else {\n <p class=\"et-query-devtools-empty\">Nothing sent or received yet.</p>\n }\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No web socket clients registered.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-ws-message{border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);padding:.6rem;margin-block-end:.4rem}.et-query-devtools-ws-message:where([data-direction=out]){border-inline-start:2px solid color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent)}.et-query-devtools-ws-direction{color:var(--_et-qdt-muted);white-space:nowrap}.et-query-devtools-ws-message:where([data-direction=out]) .et-query-devtools-ws-direction{color:var(--_et-qdt-accent)}.et-query-devtools-emit{display:flex;align-items:center;gap:.4rem;margin-block:.4rem}.et-query-devtools-emit .et-query-devtools-input{flex:0 0 14rem}.et-query-devtools-emit .et-query-devtools-input+.et-query-devtools-input{flex:1 1 auto}.et-query-devtools-emit .et-query-devtools-btn{flex:0 0 auto}}\n"], dependencies: [{ 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 }); }
4084
4534
  }
4085
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsSocketsTabComponent, decorators: [{
4535
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsSocketsTabComponent, decorators: [{
4086
4536
  type: Component,
4087
4537
  args: [{ selector: 'et-query-devtools-sockets-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsJsonComponent], template: "<div class=\"et-query-devtools-scroll\">\n @if (host.wsEntries().length) {\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #socketFilterInput\n [value]=\"host.socketFilter()\"\n (input)=\"host.socketFilter.set(socketFilterInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter event / room / direction\u2026\"\n aria-label=\"Filter socket messages\"\n />\n @if (host.socketFilter()) {\n <button\n (click)=\"host.socketFilter.set('')\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the message filter\"\n aria-label=\"Clear the message filter\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n </div>\n </div>\n }\n\n @for (entry of host.wsEntries(); track entry.id) {\n @let ws = host.asWs(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>{{ entry.meta.name || 'socket' }}</strong>\n <span\n [class.et-query-devtools-chip--success]=\"ws.connected()\"\n [class.et-query-devtools-chip--muted]=\"!ws.connected()\"\n class=\"et-query-devtools-chip\"\n >\n {{ ws.connected() ? 'connected' : 'disconnected' }}\n </span>\n </div>\n <dl class=\"et-query-devtools-meta\">\n <dt>URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ entry.meta.url }}</dd>\n <dt>Rooms</dt>\n <dd>\n @if (ws.rooms().length) {\n <div class=\"et-query-devtools-chips\">\n @for (room of ws.rooms(); track room) {\n <span class=\"et-query-devtools-chip\">{{ room }}</span>\n }\n </div>\n } @else {\n none\n }\n </dd>\n </dl>\n\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">Emit</h5>\n <span class=\"et-query-devtools-hint\">\n Sends as the app would, so a server that only answers a client that asked can be provoked from here.\n </span>\n </div>\n <div class=\"et-query-devtools-emit\">\n <input #emitEvent class=\"et-query-devtools-input\" type=\"text\" placeholder=\"event\" aria-label=\"Event to emit\" />\n <input\n #emitData\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"JSON payload (optional)\"\n aria-label=\"Payload to emit\"\n />\n <button\n (click)=\"host.emitSocketMessage({ entry, event: emitEvent.value, data: emitData.value })\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n \u2191 Send\n </button>\n </div>\n @if (host.socketEmitErrorFor(entry.id); as emitError) {\n <p class=\"et-query-devtools-editor-error\">{{ emitError }}</p>\n }\n\n @let messages = host.socketMessages(ws);\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">Messages</h5>\n <span class=\"et-query-devtools-count\">\n @if (host.socketFilter()) {\n {{ messages.length }} of {{ ws.messages().length }}\n } @else {\n {{ messages.length }}\n }\n </span>\n </div>\n @for (message of messages; track message.id) {\n <div [attr.data-direction]=\"message.direction\" class=\"et-query-devtools-ws-message\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-count\">{{ host.formatTime(message.timestamp) }}</span>\n <span class=\"et-query-devtools-ws-direction\">{{ host.socketDirectionLabel(message) }}</span>\n @if (message.room) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ message.room }}</span>\n }\n <span class=\"et-query-devtools-method\">{{ message.event }}</span>\n </div>\n @if (message.data !== undefined) {\n <et-query-devtools-json [value]=\"message.data\" />\n }\n </div>\n } @empty {\n @if (ws.messages().length) {\n <p class=\"et-query-devtools-empty\">No message matches the filter \u00B7 {{ ws.messages().length }} in the log.</p>\n } @else {\n <p class=\"et-query-devtools-empty\">Nothing sent or received yet.</p>\n }\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No web socket clients registered.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-ws-message{border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);padding:.6rem;margin-block-end:.4rem}.et-query-devtools-ws-message:where([data-direction=out]){border-inline-start:2px solid color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent)}.et-query-devtools-ws-direction{color:var(--_et-qdt-muted);white-space:nowrap}.et-query-devtools-ws-message:where([data-direction=out]) .et-query-devtools-ws-direction{color:var(--_et-qdt-accent)}.et-query-devtools-emit{display:flex;align-items:center;gap:.4rem;margin-block:.4rem}.et-query-devtools-emit .et-query-devtools-input{flex:0 0 14rem}.et-query-devtools-emit .et-query-devtools-input+.et-query-devtools-input{flex:1 1 auto}.et-query-devtools-emit .et-query-devtools-btn{flex:0 0 auto}}\n"] }]
4088
4538
  }] });
@@ -4097,10 +4547,10 @@ class QueryDevtoolsStacksTabComponent {
4097
4547
  const first = this.host.queriesForStack(stack)[0];
4098
4548
  return { method: first?.method ?? '', segments: first?.segments ?? [], baseUrl: first?.clientBaseUrl ?? '' };
4099
4549
  }
4100
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsStacksTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4101
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsStacksTabComponent, isStandalone: true, selector: "et-query-devtools-stacks-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.stackEntries(); track entry.id) {\n @if (entry.kind === 'paged-query-stack') {\n @let paged = host.asPagedStack(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">paged stack</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ paged.direction() }}</span>\n @if (paged.loading()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">loading</span>\n }\n </div>\n\n @let pagedId = stackIdentity(paged);\n <div class=\"et-query-devtools-card-title\">\n <span class=\"et-query-devtools-method\">{{ pagedId.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"pagedId.segments\" />\n </span>\n </div>\n <div class=\"et-query-devtools-card-sub et-query-devtools-mono\">{{ pagedId.baseUrl }}</div>\n @if (entry.meta.features?.length) {\n <et-query-devtools-features [features]=\"entry.meta.features ?? []\" />\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Pages loaded</dt>\n <dd>{{ paged.queries().length }}</dd>\n <dt>Items</dt>\n <dd>{{ paged.items().length }}</dd>\n <dt>First page loaded</dt>\n <dd>{{ paged.isFirstPageLoaded() }}</dd>\n <dt>Last page loaded</dt>\n <dd>{{ paged.isLastPageLoaded() }}</dd>\n <ng-container *ngTemplateOutlet=\"activityRow; context: { activity: host.stackActivity(paged) }\" />\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueries; context: { links: host.queriesForStack(paged) }\" />\n </div>\n } @else {\n @let stack = host.asStack(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">stack</span>\n @if (stack.anyLoading()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">loading</span>\n }\n @if (stack.anyError()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">error</span>\n }\n </div>\n\n @let stackId = stackIdentity(stack);\n <div class=\"et-query-devtools-card-title\">\n <span class=\"et-query-devtools-method\">{{ stackId.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"stackId.segments\" />\n </span>\n </div>\n <div class=\"et-query-devtools-card-sub et-query-devtools-mono\">{{ stackId.baseUrl }}</div>\n @if (entry.meta.features?.length) {\n <et-query-devtools-features [features]=\"entry.meta.features ?? []\" />\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Queries</dt>\n <dd>{{ stack.queries().length }}</dd>\n <dt>Loading</dt>\n <dd>{{ stack.loadingProgress().loaded }} / {{ stack.loadingProgress().total }}</dd>\n <dt>Errors</dt>\n <dd>{{ stack.errors().length }}</dd>\n <ng-container *ngTemplateOutlet=\"activityRow; context: { activity: host.stackActivity(stack) }\" />\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueries; context: { links: host.queriesForStack(stack) }\" />\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No stacks registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.stackSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.stackSelectedQueryId.set(null)\"\n />\n</div>\n\n<!-- Reusable dt/dd pair for a card's `et-query-devtools-meta` list: what its queries ran and moved. -->\n<ng-template #activityRow let-activity=\"activity\">\n <dt>Traffic</dt>\n <dd>\n @if (activity.hasActivity) {\n {{ activity.stats.requests }} request{{ activity.stats.requests === 1 ? '' : 's' }} \u00B7 \u2193\n {{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}\n @if (activity.stats.sentBytes) {\n \u00B7 \u2191 {{ host.formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}\n }\n @if (activity.cacheServed) {\n \u00B7 {{ activity.cacheServed }} from cache\n }\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n } @else {\n \u2014\n }\n </dd>\n</ng-template>\n\n<!-- Reusable inner-query list \u2014 clicking opens the split-view drawer, not a tab switch. -->\n<ng-template #innerQueries let-links=\"links\">\n <div class=\"et-query-devtools-inner-queries\">\n @for (link of links; track $index) {\n <button\n [class.et-query-devtools-inner-row--active]=\"host.stackSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"host.stackSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span [attr.data-status]=\"host.queryStatus(link.query)\" class=\"et-query-devtools-status\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" [fallback]=\"link.query.id() || 'query ' + ($index + 1)\" />\n </span>\n @let linkStats = host.linkActivity(link);\n @if (linkStats.hasActivity) {\n <span class=\"et-query-devtools-traffic\">\n {{ host.formatTransferred(linkStats.stats.receivedBytes, linkStats.stats.hasEstimatedBytes) }}\n </span>\n }\n </button>\n }\n </div>\n</ng-template>\n", styles: ["@layer components{.et-query-devtools-traffic{margin-inline-start:auto;flex:0 0 auto;white-space:nowrap;font-variant-numeric:tabular-nums;color:var(--_et-qdt-subtle)}.et-query-devtools-card-title{display:flex;align-items:baseline;gap:.5rem;margin-block-end:.2rem}.et-query-devtools-card-sub{color:var(--_et-qdt-subtle);font-size:1.1rem;margin-block-end:.6rem;overflow-wrap:anywhere}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }, { kind: "component", type: QueryDevtoolsFeaturesComponent, selector: "et-query-devtools-features", inputs: ["features"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
4550
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsStacksTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4551
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsStacksTabComponent, isStandalone: true, selector: "et-query-devtools-stacks-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.stackEntries(); track entry.id) {\n @if (entry.kind === 'paged-query-stack') {\n @let paged = host.asPagedStack(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">paged stack</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ paged.direction() }}</span>\n @if (paged.loading()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">loading</span>\n }\n </div>\n\n @let pagedId = stackIdentity(paged);\n <div class=\"et-query-devtools-card-title\">\n <span class=\"et-query-devtools-method\">{{ pagedId.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"pagedId.segments\" />\n </span>\n </div>\n <div class=\"et-query-devtools-card-sub et-query-devtools-mono\">{{ pagedId.baseUrl }}</div>\n @if (entry.meta.features?.length) {\n <et-query-devtools-features [features]=\"entry.meta.features ?? []\" />\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Pages loaded</dt>\n <dd>{{ paged.queries().length }}</dd>\n <dt>Items</dt>\n <dd>{{ paged.items().length }}</dd>\n <dt>First page loaded</dt>\n <dd>{{ paged.isFirstPageLoaded() }}</dd>\n <dt>Last page loaded</dt>\n <dd>{{ paged.isLastPageLoaded() }}</dd>\n <ng-container *ngTemplateOutlet=\"activityRow; context: { activity: host.stackActivity(paged) }\" />\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueries; context: { links: host.queriesForStack(paged) }\" />\n </div>\n } @else {\n @let stack = host.asStack(entry);\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">stack</span>\n @if (stack.anyLoading()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--loading\">loading</span>\n }\n @if (stack.anyError()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">error</span>\n }\n </div>\n\n @let stackId = stackIdentity(stack);\n <div class=\"et-query-devtools-card-title\">\n <span class=\"et-query-devtools-method\">{{ stackId.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"stackId.segments\" />\n </span>\n </div>\n <div class=\"et-query-devtools-card-sub et-query-devtools-mono\">{{ stackId.baseUrl }}</div>\n @if (entry.meta.features?.length) {\n <et-query-devtools-features [features]=\"entry.meta.features ?? []\" />\n }\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Queries</dt>\n <dd>{{ stack.queries().length }}</dd>\n <dt>Loading</dt>\n <dd>{{ stack.loadingProgress().loaded }} / {{ stack.loadingProgress().total }}</dd>\n <dt>Errors</dt>\n <dd>{{ stack.errors().length }}</dd>\n <ng-container *ngTemplateOutlet=\"activityRow; context: { activity: host.stackActivity(stack) }\" />\n </dl>\n <ng-container *ngTemplateOutlet=\"innerQueries; context: { links: host.queriesForStack(stack) }\" />\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No stacks registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.stackSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.stackSelectedQueryId.set(null)\"\n />\n</div>\n\n<!-- Reusable dt/dd pair for a card's `et-query-devtools-meta` list: what its queries ran and moved. -->\n<ng-template #activityRow let-activity=\"activity\">\n <dt>Traffic</dt>\n <dd>\n @if (activity.hasActivity) {\n {{ activity.stats.requests }} request{{ activity.stats.requests === 1 ? '' : 's' }} \u00B7 \u2193\n {{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}\n @if (activity.stats.sentBytes) {\n \u00B7 \u2191 {{ host.formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}\n }\n @if (activity.cacheServed) {\n \u00B7 {{ activity.cacheServed }} from cache\n }\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n } @else {\n \u2014\n }\n </dd>\n</ng-template>\n\n<!-- Reusable inner-query list \u2014 clicking opens the split-view drawer, not a tab switch. -->\n<ng-template #innerQueries let-links=\"links\">\n <div class=\"et-query-devtools-inner-queries\">\n @for (link of links; track $index) {\n <button\n [class.et-query-devtools-inner-row--active]=\"host.stackSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"host.stackSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span [attr.data-status]=\"host.queryStatus(link.query)\" class=\"et-query-devtools-status\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" [fallback]=\"link.query.id() || 'query ' + ($index + 1)\" />\n </span>\n @let linkStats = host.linkActivity(link);\n @if (linkStats.hasActivity) {\n <span class=\"et-query-devtools-traffic\">\n {{ host.formatTransferred(linkStats.stats.receivedBytes, linkStats.stats.hasEstimatedBytes) }}\n </span>\n }\n </button>\n }\n </div>\n</ng-template>\n", styles: ["@layer components{.et-query-devtools-traffic{margin-inline-start:auto;flex:0 0 auto;white-space:nowrap;font-variant-numeric:tabular-nums;color:var(--_et-qdt-subtle)}.et-query-devtools-card-title{display:flex;align-items:baseline;gap:.5rem;margin-block-end:.2rem}.et-query-devtools-card-sub{color:var(--_et-qdt-subtle);font-size:1.1rem;margin-block-end:.6rem;overflow-wrap:anywhere}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }, { kind: "component", type: QueryDevtoolsFeaturesComponent, selector: "et-query-devtools-features", inputs: ["features"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
4102
4552
  }
4103
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsStacksTabComponent, decorators: [{
4553
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsStacksTabComponent, decorators: [{
4104
4554
  type: Component,
4105
4555
  args: [{ selector: 'et-query-devtools-stacks-tab', encapsulation: ViewEncapsulation.None, imports: [
4106
4556
  NgTemplateOutlet,
@@ -4193,88 +4643,14 @@ class QueryDevtoolsTimelineTabComponent {
4193
4643
  selectTimelineRow(row) {
4194
4644
  this.host.timelineSelectedQueryId.set(row.entryId);
4195
4645
  }
4196
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsTimelineTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4197
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsTimelineTabComponent, isStandalone: true, selector: "et-query-devtools-timeline-tab", ngImport: i0, template: "@let tl = timeline();\n<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (host.inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n } @else {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #timelineClientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(timelineClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\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 </label>\n }\n <span class=\"et-query-devtools-count\">\n {{ tl.rows.length }} run{{ tl.rows.length === 1 ? '' : 's' }}\n @if (tl.hidden) {\n \u00B7 {{ tl.hidden }} older not shown\n }\n @if (tl.rows.length) {\n \u00B7 over {{ host.formatDuration(tl.windowMs) }} from {{ host.formatTime(tl.startedAt) }}\n }\n </span>\n <button\n [disabled]=\"!tl.rows.length\"\n (click)=\"resetTimeline()\"\n class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\"\n type=\"button\"\n title=\"Clear the run history and the counters of every listed query\"\n >\n \u27F2 Reset\n </button>\n </div>\n </div>\n\n @if (tl.rows.length) {\n <div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-timeline\">\n <div class=\"et-query-devtools-timeline-axis\">\n <span class=\"et-query-devtools-timeline-ticks\">\n @for (tick of timelineTicks(); track tick.pct) {\n <span [style.left.%]=\"tick.pct\" class=\"et-query-devtools-timeline-tick\">{{ tick.label }}</span>\n }\n </span>\n </div>\n\n @for (row of tl.rows; track row.key) {\n <button\n [class.et-query-devtools-timeline-row--active]=\"host.timelineSelectedQueryId() === row.entryId\"\n (click)=\"selectTimelineRow(row)\"\n class=\"et-query-devtools-timeline-row\"\n type=\"button\"\n title=\"{{ row.method }} {{ row.path }} \u00B7 run {{ row.run.index }} \u00B7 {{ row.run.status }}\"\n >\n <span class=\"et-query-devtools-timeline-label\">\n <span class=\"et-query-devtools-method\">{{ row.method }}</span>\n <span class=\"et-query-devtools-url\">{{ row.path }}</span>\n </span>\n <span class=\"et-query-devtools-timeline-track\">\n <span\n [attr.data-status]=\"host.runStatus(row.run)\"\n [style.left.%]=\"row.leftPct\"\n [style.width.%]=\"row.widthPct\"\n class=\"et-query-devtools-timeline-bar\"\n ></span>\n </span>\n <span class=\"et-query-devtools-timeline-duration\">\n @if (row.durationMs === null) {\n in flight\n } @else {\n {{ host.formatDuration(row.durationMs) }}\n }\n </span>\n <span class=\"et-query-devtools-timeline-size\">\n @if (row.run.receivedBytes) {\n {{ host.formatBytes(row.run.receivedBytes) }}\n }\n </span>\n <span class=\"et-query-devtools-timeline-flags\">\n @if (row.run.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Took {{\n row.run.attempts\n }} attempts - so this bar is mostly retry backoff, not one slow round trip\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ row.run.attempts }}\n </span>\n }\n @if (!row.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 (row.run.status === 'aborted') {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"The query requested again before this run's response arrived\"\n >\n aborted\n </span>\n }\n </span>\n </button>\n }\n </div>\n </div>\n } @else {\n <p class=\"et-query-devtools-empty\">\n No requests recorded yet. Every request a listed query makes shows up here as a bar.\n </p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.timelineSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.timelineSelectedQueryId.set(null)\"\n />\n</div>\n", dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }], encapsulation: i0.ViewEncapsulation.None }); }
4646
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsTimelineTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4647
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsTimelineTabComponent, isStandalone: true, selector: "et-query-devtools-timeline-tab", ngImport: i0, template: "@let tl = timeline();\n<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (host.inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n } @else {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #timelineClientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(timelineClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\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 </label>\n }\n <span class=\"et-query-devtools-count\">\n {{ tl.rows.length }} run{{ tl.rows.length === 1 ? '' : 's' }}\n @if (tl.hidden) {\n \u00B7 {{ tl.hidden }} older not shown\n }\n @if (tl.rows.length) {\n \u00B7 over {{ host.formatDuration(tl.windowMs) }} from {{ host.formatTime(tl.startedAt) }}\n }\n </span>\n <button\n [disabled]=\"!tl.rows.length\"\n (click)=\"resetTimeline()\"\n class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\"\n type=\"button\"\n title=\"Clear the run history and the counters of every listed query\"\n >\n \u27F2 Reset\n </button>\n </div>\n </div>\n\n @if (tl.rows.length) {\n <div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-timeline\">\n <div class=\"et-query-devtools-timeline-axis\">\n <span class=\"et-query-devtools-timeline-ticks\">\n @for (tick of timelineTicks(); track tick.pct) {\n <span [style.left.%]=\"tick.pct\" class=\"et-query-devtools-timeline-tick\">{{ tick.label }}</span>\n }\n </span>\n </div>\n\n @for (row of tl.rows; track row.key) {\n <button\n [class.et-query-devtools-timeline-row--active]=\"host.timelineSelectedQueryId() === row.entryId\"\n (click)=\"selectTimelineRow(row)\"\n class=\"et-query-devtools-timeline-row\"\n type=\"button\"\n title=\"{{ row.method }} {{ row.path }} \u00B7 run {{ row.run.index }} \u00B7 {{ row.run.status }}\"\n >\n <span class=\"et-query-devtools-timeline-label\">\n <span class=\"et-query-devtools-method\">{{ row.method }}</span>\n <span class=\"et-query-devtools-url\">{{ row.path }}</span>\n </span>\n <span class=\"et-query-devtools-timeline-track\">\n <span\n [attr.data-status]=\"host.runStatus(row.run)\"\n [style.left.%]=\"row.leftPct\"\n [style.width.%]=\"row.widthPct\"\n class=\"et-query-devtools-timeline-bar\"\n ></span>\n </span>\n <span class=\"et-query-devtools-timeline-duration\">\n @if (row.durationMs === null) {\n in flight\n } @else {\n {{ host.formatDuration(row.durationMs) }}\n }\n </span>\n <span class=\"et-query-devtools-timeline-size\">\n @if (row.run.receivedBytes) {\n {{ host.formatBytes(row.run.receivedBytes) }}\n }\n </span>\n <span class=\"et-query-devtools-timeline-flags\">\n @if (row.run.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Took {{\n row.run.attempts\n }} attempts - so this bar is mostly retry backoff, not one slow round trip\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ row.run.attempts }}\n </span>\n }\n @if (!row.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 (row.run.status === 'aborted') {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"The query requested again before this run's response arrived\"\n >\n aborted\n </span>\n }\n </span>\n </button>\n }\n </div>\n </div>\n } @else {\n <p class=\"et-query-devtools-empty\">\n No requests recorded yet. Every request a listed query makes shows up here as a bar.\n </p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.timelineSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.timelineSelectedQueryId.set(null)\"\n />\n</div>\n", dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }], encapsulation: i0.ViewEncapsulation.None }); }
4198
4648
  }
4199
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsTimelineTabComponent, decorators: [{
4649
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsTimelineTabComponent, decorators: [{
4200
4650
  type: Component,
4201
4651
  args: [{ selector: 'et-query-devtools-timeline-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDrawerComponent], template: "@let tl = timeline();\n<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (host.inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n } @else {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #timelineClientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(timelineClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\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 </label>\n }\n <span class=\"et-query-devtools-count\">\n {{ tl.rows.length }} run{{ tl.rows.length === 1 ? '' : 's' }}\n @if (tl.hidden) {\n \u00B7 {{ tl.hidden }} older not shown\n }\n @if (tl.rows.length) {\n \u00B7 over {{ host.formatDuration(tl.windowMs) }} from {{ host.formatTime(tl.startedAt) }}\n }\n </span>\n <button\n [disabled]=\"!tl.rows.length\"\n (click)=\"resetTimeline()\"\n class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\"\n type=\"button\"\n title=\"Clear the run history and the counters of every listed query\"\n >\n \u27F2 Reset\n </button>\n </div>\n </div>\n\n @if (tl.rows.length) {\n <div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-timeline\">\n <div class=\"et-query-devtools-timeline-axis\">\n <span class=\"et-query-devtools-timeline-ticks\">\n @for (tick of timelineTicks(); track tick.pct) {\n <span [style.left.%]=\"tick.pct\" class=\"et-query-devtools-timeline-tick\">{{ tick.label }}</span>\n }\n </span>\n </div>\n\n @for (row of tl.rows; track row.key) {\n <button\n [class.et-query-devtools-timeline-row--active]=\"host.timelineSelectedQueryId() === row.entryId\"\n (click)=\"selectTimelineRow(row)\"\n class=\"et-query-devtools-timeline-row\"\n type=\"button\"\n title=\"{{ row.method }} {{ row.path }} \u00B7 run {{ row.run.index }} \u00B7 {{ row.run.status }}\"\n >\n <span class=\"et-query-devtools-timeline-label\">\n <span class=\"et-query-devtools-method\">{{ row.method }}</span>\n <span class=\"et-query-devtools-url\">{{ row.path }}</span>\n </span>\n <span class=\"et-query-devtools-timeline-track\">\n <span\n [attr.data-status]=\"host.runStatus(row.run)\"\n [style.left.%]=\"row.leftPct\"\n [style.width.%]=\"row.widthPct\"\n class=\"et-query-devtools-timeline-bar\"\n ></span>\n </span>\n <span class=\"et-query-devtools-timeline-duration\">\n @if (row.durationMs === null) {\n in flight\n } @else {\n {{ host.formatDuration(row.durationMs) }}\n }\n </span>\n <span class=\"et-query-devtools-timeline-size\">\n @if (row.run.receivedBytes) {\n {{ host.formatBytes(row.run.receivedBytes) }}\n }\n </span>\n <span class=\"et-query-devtools-timeline-flags\">\n @if (row.run.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Took {{\n row.run.attempts\n }} attempts - so this bar is mostly retry backoff, not one slow round trip\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ row.run.attempts }}\n </span>\n }\n @if (!row.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 (row.run.status === 'aborted') {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"The query requested again before this run's response arrived\"\n >\n aborted\n </span>\n }\n </span>\n </button>\n }\n </div>\n </div>\n } @else {\n <p class=\"et-query-devtools-empty\">\n No requests recorded yet. Every request a listed query makes shows up here as a bar.\n </p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.timelineSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.timelineSelectedQueryId.set(null)\"\n />\n</div>\n" }]
4202
4652
  }], ctorParameters: () => [] });
4203
4653
 
4204
- /**
4205
- * How deep a value is walked before it is replaced by a marker, and how much of a long string or a long
4206
- * array survives. A report is read by a person, so a 5000-item list is worth two entries and a count.
4207
- */
4208
- const MAX_DEPTH = 6;
4209
- const MAX_STRING = 200;
4210
- const MAX_ARRAY_ITEMS = 2;
4211
- /**
4212
- * Slims a value for a shareable report: long strings are truncated and long arrays keep only the first
4213
- * couple of entries, replacing the repetitive tail with a `… (N more)` marker, so a big response
4214
- * collapses to a representative sample.
4215
- */
4216
- const slimForReport = (value, depth = 0) => {
4217
- if (typeof value === 'string')
4218
- return value.length > MAX_STRING ? `${value.slice(0, MAX_STRING)}…` : value;
4219
- if (depth > MAX_DEPTH)
4220
- return '…';
4221
- if (Array.isArray(value)) {
4222
- if (value.length > MAX_ARRAY_ITEMS + 1) {
4223
- return [
4224
- ...value.slice(0, MAX_ARRAY_ITEMS).map((entry) => slimForReport(entry, depth + 1)),
4225
- `… (${value.length - MAX_ARRAY_ITEMS} more)`,
4226
- ];
4227
- }
4228
- return value.map((entry) => slimForReport(entry, depth + 1));
4229
- }
4230
- if (value && typeof value === 'object') {
4231
- const out = {};
4232
- for (const [key, entry] of Object.entries(value))
4233
- out[key] = slimForReport(entry, depth + 1);
4234
- return out;
4235
- }
4236
- return value;
4237
- };
4238
- /** Slims the free-form value fields of an entry, leaving the rest as it was collected. */
4239
- const slimEntry = (entry) => ({
4240
- ...entry,
4241
- ...('args' in entry ? { args: slimForReport(entry.args) } : {}),
4242
- ...('response' in entry ? { response: slimForReport(entry.response) } : {}),
4243
- ...('error' in entry ? { error: slimForReport(entry.error) } : {}),
4244
- ...(entry.detail ? { detail: slimForReport(entry.detail) } : {}),
4245
- ...(entry.overrides?.length ? { overrides: slimForReport(entry.overrides) } : {}),
4246
- });
4247
- /**
4248
- * Builds the whole-session report: every registered entry with what it ran and what it holds, the event
4249
- * log, the cache totals per client and anything armed in the Faults or Mocks tabs or as a response
4250
- * override.
4251
- *
4252
- * Bodies are slimmed rather than dumped in full - the point is a file small enough to attach to a bug
4253
- * report, and a 4 MB response says nothing a representative sample does not. Armed faults and overrides
4254
- * are included because a report captured while the panel was lying to the app has to say so. Neither
4255
- * survives past the current page - this is a snapshot for a bug report, not something the panel can
4256
- * later import to restore a session.
4257
- */
4258
- const buildQueryDevtoolsSessionExport = (options) => ({
4259
- _type: 'ethlete.query:devtools-session',
4260
- exportedAt: new Date(options.now).toISOString(),
4261
- location: options.location,
4262
- about: options.about,
4263
- counts: {
4264
- clients: options.clients.length,
4265
- entries: options.entries.length,
4266
- events: options.events.length,
4267
- armedFaults: options.faults.length,
4268
- armedOverrides: options.entries.reduce((sum, entry) => sum + (entry.overrides?.length ?? 0), 0),
4269
- armedMocks: options.mocks.length,
4270
- },
4271
- clients: options.clients,
4272
- entries: options.entries.map(slimEntry),
4273
- events: options.events,
4274
- faults: options.faults,
4275
- mocks: options.mocks,
4276
- });
4277
-
4278
4654
  /** How long a copy button stays ticked after a successful write. */
4279
4655
  const COPIED_RESET_MS = 1200;
4280
4656
  /**
@@ -4344,7 +4720,6 @@ const DOCK_PADDING = {
4344
4720
  * of the space a docked panel covers; the panel's own overlays paint a level above it and may use it.
4345
4721
  */
4346
4722
  const PANEL_LAYER = 2147483010;
4347
- const noop = () => undefined;
4348
4723
  /**
4349
4724
  * A devtools entry id as a person reads it. Ids are the registry's descriptor plus a per-descriptor
4350
4725
  * sequence number (`query|<client>|<method>|<route>#<n>`), so the parts are all there - just not in an
@@ -4584,23 +4959,6 @@ const executableValue = (draft, source) => {
4584
4959
  }
4585
4960
  return draft;
4586
4961
  };
4587
- /**
4588
- * The JSONPath of a string value inside a response, or `null`. Used to locate the access token in an
4589
- * auth response whose shape only the provider's `extractTokens` knows.
4590
- */
4591
- const findValuePath = (value, node) => {
4592
- if (node.value === value)
4593
- return node.path;
4594
- if (node.depth > 5 || !node.value || typeof node.value !== 'object')
4595
- return null;
4596
- for (const [key, entry] of Object.entries(node.value)) {
4597
- const path = Array.isArray(node.value) ? `${node.path}[${key}]` : `${node.path}.${key}`;
4598
- const found = findValuePath(value, { value: entry, path, depth: node.depth + 1 });
4599
- if (found)
4600
- return found;
4601
- }
4602
- return null;
4603
- };
4604
4962
  /** A countdown to an `exp` claim in seconds-since-epoch, or `null` when there is nothing to count to. */
4605
4963
  const expiryCountdown = (exp) => {
4606
4964
  if (exp === null)
@@ -4842,6 +5200,13 @@ class QueryDevtoolsComponent {
4842
5200
  /** The form whose detail the Forms tab has expanded. */
4843
5201
  this.selectedFormId = signal(this.persisted.selectedFormId ?? null, /* @ts-ignore */
4844
5202
  ...(ngDevMode ? [{ debugName: "selectedFormId" }] : /* istanbul ignore next */ []));
5203
+ /**
5204
+ * The type annotations of each seeded mock body, by mock id. It lives here rather than on the Mocks
5205
+ * tab because a tab is destroyed the moment another one is selected, and a body seeded from an inline
5206
+ * schema has no `schemaName` to recover its labels from.
5207
+ */
5208
+ this.seededTypes = signal({}, /* @ts-ignore */
5209
+ ...(ngDevMode ? [{ debugName: "seededTypes" }] : /* istanbul ignore next */ []));
4845
5210
  // Independent per-drawer selection so no drawer shares the Queries tab's selection - or another
4846
5211
  // drawer's: opening a query from the Timeline must not also change what the Forms drawer shows. Each
4847
5212
  // is persisted the way the Queries tab's is, so a reload puts every drawer back on what it was showing.
@@ -4973,8 +5338,13 @@ class QueryDevtoolsComponent {
4973
5338
  this.copiedRoute = signal(false, /* @ts-ignore */
4974
5339
  ...(ngDevMode ? [{ debugName: "copiedRoute" }] : /* istanbul ignore next */ []));
4975
5340
  this.copiedReset$ = new Subject();
4976
- /** 1-second tick driving the cache freshness countdowns. */
4977
- this.clock = toSignal(interval(1000), { initialValue: 0 });
5341
+ /**
5342
+ * 1-second tick driving the cache freshness countdowns. Gated the way the locks poll is: an ungated
5343
+ * `interval` is a periodic macrotask zone.js counts forever, so a mounted panel would tick the host
5344
+ * application once a second and keep `ApplicationRef.whenStable()` from ever resolving. Every reader
5345
+ * sits inside `@if (open())`, so there is nothing to drive while the panel is shut or the tab hidden.
5346
+ */
5347
+ this.clock = toSignal(toObservable(computed(() => this.open() && this.isDocumentVisible())).pipe(switchMap((ticking) => (ticking ? interval(1000) : EMPTY)), scan((tick) => tick + 1, 0)), { initialValue: 0 });
4978
5348
  /**
4979
5349
  * The probe lock behind every Locks row's "this tab" answer, and the last snapshot read through it.
4980
5350
  * Both only exist while the Locks tab is on screen - see the polling stream in the constructor.
@@ -5042,12 +5412,16 @@ class QueryDevtoolsComponent {
5042
5412
  return Array.from(names).sort();
5043
5413
  }, /* @ts-ignore */
5044
5414
  ...(ngDevMode ? [{ debugName: "clientNames" }] : /* istanbul ignore next */ []));
5045
- /** Unique repositories (with their client name + base URL) used by the Cache and Events tabs. */
5415
+ /**
5416
+ * Unique repositories (with their client name + base URL) used by the Cache and Events tabs. A
5417
+ * tombstone keeps its `meta.repository`, so a destroyed client would otherwise keep its Cache card
5418
+ * and its **Evict all** long after its injector died.
5419
+ */
5046
5420
  this.repositories = computed(() => {
5047
5421
  const map = new Map();
5048
5422
  for (const entry of queryDevtoolsEntries()) {
5049
5423
  const repo = entry.meta.repository;
5050
- if (repo && !map.has(repo)) {
5424
+ if (repo && !map.has(repo) && isQueryDevtoolsRepositoryLive(repo)) {
5051
5425
  map.set(repo, {
5052
5426
  name: entry.meta.clientName ?? 'unknown',
5053
5427
  baseUrl: entry.meta.clientBaseUrl ?? '',
@@ -5461,6 +5835,9 @@ class QueryDevtoolsComponent {
5461
5835
  registerEthleteVersion('query-devtools', QUERY_DEVTOOLS_VERSION);
5462
5836
  // A pop-out holds the panel element; leaving its window open would leave a dead panel on screen.
5463
5837
  this.destroyRef.onDestroy(() => this.closePopup());
5838
+ // The poll above ends on `takeUntilDestroyed`, which unsubscribes without a final `false`, so
5839
+ // without this the probe lock stays held for the life of the page.
5840
+ this.destroyRef.onDestroy(() => this.holdProbeLock(false));
5464
5841
  const doc = this.document;
5465
5842
  // Angular does not destroy the application when the host page unloads, so the clean-up above never
5466
5843
  // runs on a reload - the pop-up would outlive the document whose panel it holds and keep showing a
@@ -5967,6 +6344,9 @@ class QueryDevtoolsComponent {
5967
6344
  return value.length > 80 ? `"${value.slice(0, 80)}…"` : `"${value}"`;
5968
6345
  if (value === undefined)
5969
6346
  return 'undefined';
6347
+ const exotic = inlineExoticOf(value);
6348
+ if (exotic)
6349
+ return exotic;
5970
6350
  try {
5971
6351
  const json = JSON.stringify(value) ?? 'undefined';
5972
6352
  return json.length > 80 ? `${json.slice(0, 80)}…` : json;
@@ -6038,7 +6418,7 @@ class QueryDevtoolsComponent {
6038
6418
  const gqlDoc = entry.meta.gqlQuery ? this.gqlDocument(entry.meta.gqlQuery) : null;
6039
6419
  const args = this.queryArgs(query);
6040
6420
  const argsLabel = gqlDoc ? 'Variables' : 'Args';
6041
- const argsJson = args !== null && args !== undefined ? JSON.stringify(args, null, 2) : null;
6421
+ const argsJson = args !== null && args !== undefined ? JSON.stringify(slimForReport(args), null, 2) : null;
6042
6422
  const bodyLabel = error ? `Error (${error.raw.status})` : 'Response';
6043
6423
  const bodyContent = error
6044
6424
  ? error.isList
@@ -6135,8 +6515,8 @@ class QueryDevtoolsComponent {
6135
6515
  * holds, the event log, the cache totals and anything armed in the Faults tab. Unlike **Copy report**
6136
6516
  * this is not scoped to one query - it is the attachment for a bug report about a screen.
6137
6517
  *
6138
- * Deliberately unfiltered: a report is read by someone who was not there, and a dump that silently
6139
- * left out the client you were not looking at is worse than no dump.
6518
+ * Deliberately unfiltered apart from credentials: a report is read by someone who was not there, and a
6519
+ * dump that silently left out the client you were not looking at is worse than no dump.
6140
6520
  */
6141
6521
  downloadSession() {
6142
6522
  const now = Date.now();
@@ -6149,6 +6529,7 @@ class QueryDevtoolsComponent {
6149
6529
  events: this.sessionEvents(),
6150
6530
  faults: this.sessionFaults(),
6151
6531
  mocks: this.sessionMocks(),
6532
+ authQueryKeys: this.sessionAuthQueryKeys(),
6152
6533
  }), null, 2);
6153
6534
  this.downloadFile(`query-devtools-session-${now}.json`, json);
6154
6535
  }
@@ -6686,9 +7067,7 @@ class QueryDevtoolsComponent {
6686
7067
  try {
6687
7068
  if (request)
6688
7069
  return request.subtle.resolveHeaders();
6689
- return typeof args?.headers === 'function'
6690
- ? args.headers()
6691
- : args?.headers;
7070
+ return resolveQueryHeaders(args?.headers);
6692
7071
  }
6693
7072
  catch {
6694
7073
  return undefined;
@@ -6864,7 +7243,7 @@ class QueryDevtoolsComponent {
6864
7243
  };
6865
7244
  for (const node of Array.from(this.document.head.children))
6866
7245
  copy(node);
6867
- // eslint-disable-next-line ethlete/no-native-observers -- signalElementMutations reports only the first record of a batch (styles often arrive several per task), and this observer lives with the pop-out, not the component
7246
+ // eslint-disable-next-line ethlete/no-native-observers -- this observer lives with the pop-out window, not with the component
6868
7247
  const observer = new MutationObserver((mutations) => {
6869
7248
  for (const mutation of mutations) {
6870
7249
  mutation.removedNodes.forEach(drop);
@@ -6993,7 +7372,7 @@ class QueryDevtoolsComponent {
6993
7372
  accessTokenPath(provider) {
6994
7373
  const token = provider.accessToken();
6995
7374
  const response = provider.latestExecutedQuery()?.snapshot.response();
6996
- const path = token && response ? findValuePath(token, { value: response, path: '$', depth: 0 }) : null;
7375
+ const path = token && response ? findInsomniaValuePath(token, response) : null;
6997
7376
  return path ?? '$.accessToken';
6998
7377
  }
6999
7378
  /**
@@ -7031,6 +7410,20 @@ class QueryDevtoolsComponent {
7031
7410
  features: (client.client?.subtle.devtoolsFeatures ?? []).map((feature) => this.featureSummary(feature)),
7032
7411
  }));
7033
7412
  }
7413
+ /**
7414
+ * A bearer auth provider's login and token-refresh queries are ordinary registered queries, so the
7415
+ * export has to be told which ones they are before it can leave their bodies out. The provider's own
7416
+ * entry lists them by client, method and route, which is what a query entry is matched on.
7417
+ */
7418
+ sessionAuthQueryKeys() {
7419
+ return queryDevtoolsEntries()
7420
+ .filter((entry) => entry.kind === 'auth-provider')
7421
+ .flatMap((entry) => (entry.meta.authQueries ?? []).map((authQuery) => sessionAuthQueryKey({
7422
+ client: entry.meta.clientBaseUrl ?? entry.meta.clientName ?? null,
7423
+ method: authQuery.method,
7424
+ route: authQuery.route,
7425
+ })));
7426
+ }
7034
7427
  /** Every registered entry, described by whatever its kind carries. */
7035
7428
  sessionEntries() {
7036
7429
  return queryDevtoolsEntries().map((entry) => {
@@ -7320,27 +7713,12 @@ class QueryDevtoolsComponent {
7320
7713
  }
7321
7714
  /** Writes to the clipboard and ticks `copied` on success. `html` is omitted for plain-text payloads. */
7322
7715
  writeToClipboard(payload, copied) {
7323
- const clipboard = navigator.clipboard;
7324
- const { text, html } = payload;
7325
- if (!clipboard)
7326
- return;
7327
- const flag = () => {
7716
+ writeQueryDevtoolsClipboard(payload).then((result) => {
7717
+ if (!result.ok)
7718
+ return;
7328
7719
  copied.set(true);
7329
7720
  this.copiedReset$.next();
7330
- };
7331
- // Prefer rich HTML (Slack keeps the formatting on paste); fall back to plain text.
7332
- if (html !== undefined && 'write' in clipboard && typeof ClipboardItem !== 'undefined') {
7333
- const item = new ClipboardItem({
7334
- 'text/html': new Blob([html], { type: 'text/html' }),
7335
- 'text/plain': new Blob([text], { type: 'text/plain' }),
7336
- });
7337
- clipboard
7338
- .write([item])
7339
- .then(flag)
7340
- .catch(() => clipboard.writeText(text).then(flag).catch(noop));
7341
- return;
7342
- }
7343
- clipboard.writeText(text).then(flag).catch(noop);
7721
+ });
7344
7722
  }
7345
7723
  responseStatus(query) {
7346
7724
  const event = query.latestHttpEvent();
@@ -7470,10 +7848,10 @@ class QueryDevtoolsComponent {
7470
7848
  pushEventItem(item) {
7471
7849
  this.eventLog.update((log) => [item, ...log].slice(0, queryDevtoolsSettings().maxEvents));
7472
7850
  }
7473
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7474
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsComponent, isStandalone: true, selector: "et-query-devtools", inputs: { startOpen: { classPropertyName: "startOpen", publicName: "startOpen", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-et-overlay-layer": "2147483020" }, properties: { "attr.data-dock": "dock()" }, classAttribute: "et-query-devtools-host" }, providers: [{ provide: QUERY_DEVTOOLS_HOST, useExisting: QueryDevtoolsComponent }], viewQueries: [{ propertyName: "panelEl", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (open()) {\n <section\n #panel\n [class.et-query-devtools-panel--resizing]=\"resizing()\"\n [attr.data-dock]=\"poppedOut() ? 'popout' : dock()\"\n [attr.data-pane-axis]=\"paneAxis()\"\n [attr.data-parked]=\"floatParked() ? '' : null\"\n [style.block-size.px]=\"panelBlockSize()\"\n [style.inline-size.px]=\"panelInlineSize()\"\n [style.inset-block-start.px]=\"panelInsetBlock()\"\n [style.inset-inline-start.px]=\"panelInsetInline()\"\n [style.--_et-qdt-list-width.px]=\"listWidth()\"\n [style.--_et-qdt-drawer-width.px]=\"drawerWidth()\"\n [style.--_et-qdt-list-height.px]=\"listHeight()\"\n [style.--_et-qdt-drawer-height.px]=\"drawerHeight()\"\n class=\"et-query-devtools-panel\"\n >\n <!-- A pop-out is sized by its window, so there is no edge to drag. A float is a window in the page:\n a title bar to move it and every edge to resize it, on the same `@ethlete/core` primitives the\n stream pip window uses. A dock has one edge, and it does both. -->\n @if (floating()) {\n <!-- Parked, the panel is a grab strip: the edge handles would eat most of the peek that is left\n to click, and resizing something you cannot see is not a thing to offer. -->\n <et-resize-handles\n [edges]=\"FLOAT_RESIZE_EDGES\"\n [disabled]=\"floatParked()\"\n (resizeStarted)=\"startFloatResize()\"\n (resizeMoved)=\"resizeFloat($event)\"\n (resizeEnded)=\"endFloatResize()\"\n (resizeCancelled)=\"endFloatResize()\"\n />\n <div\n (dragMoved)=\"moveFloat($event)\"\n (dragEnded)=\"endFloatMove()\"\n (dragCancelled)=\"endFloatMove()\"\n (dragTapped)=\"restoreFloat()\"\n class=\"et-query-devtools-move\"\n title=\"Drag to move \u2014 shove it off an edge to park it, click the strip to bring it back\"\n aria-hidden=\"true\"\n etDragHandle\n ></div>\n } @else if (!poppedOut()) {\n <div\n (pointerdown)=\"startResize($event)\"\n class=\"et-query-devtools-resize\"\n title=\"Drag to resize\"\n aria-hidden=\"true\"\n ></div>\n }\n <header class=\"et-query-devtools-header\">\n <nav class=\"et-query-devtools-tabs\" role=\"tablist\">\n @for (tab of visibleTabs(); track tab.id) {\n @let badge = tabBadges()[tab.id];\n <button\n [class.et-query-devtools-tab--active]=\"activeTab() === tab.id\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n (click)=\"activeTab.set(tab.id)\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (badge.count) {\n <span class=\"et-query-devtools-tab-badge\">{{ badge.count }}</span>\n }\n @if (badge.errors) {\n <span\n class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\"\n title=\"{{ badge.errors }} {{ badge.errorNoun ?? 'failing' }}\"\n >\n {{ badge.errors }}\n </span>\n }\n </button>\n }\n\n @if (overflowTabs().length; as overflowCount) {\n <div class=\"et-query-devtools-menu\" etMenu>\n <button\n [title]=\"overflowCount + ' tabs with nothing in them'\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n etMenuTrigger\n >\n More\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n @for (tab of overflowTabs(); track tab.id) {\n <button (activate)=\"activeTab.set(tab.id)\" et-menu-item type=\"button\">{{ tab.label }}</button>\n }\n </et-menu>\n </ng-template>\n </div>\n }\n </nav>\n\n <div class=\"et-query-devtools-header-actions\">\n <button\n [class.et-query-devtools-inspect-btn--active]=\"inspectActive()\"\n [attr.aria-pressed]=\"inspectActive()\"\n (click)=\"toggleInspect()\"\n class=\"et-query-devtools-btn et-query-devtools-inspect-btn\"\n type=\"button\"\n title=\"Inspect: hover the UI to find its query\"\n >\n <svg\n class=\"et-query-devtools-inspect-icon\"\n viewBox=\"0 0 16 16\"\n width=\"14\"\n height=\"14\"\n fill=\"none\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <circle class=\"et-query-devtools-inspect-icon-ring\" cx=\"8\" cy=\"8\" r=\"4.25\" />\n <circle class=\"et-query-devtools-inspect-icon-dot\" cx=\"8\" cy=\"8\" r=\"1.15\" />\n <path d=\"M8 0.9v2.1M8 13v2.1M0.9 8h2.1M13 8h2.1\" />\n </svg>\n {{ inspectActive() ? 'Inspecting' : 'Inspect' }}\n @if (inspectActive()) {\n <kbd class=\"et-query-devtools-kbd et-query-devtools-kbd--inverse\">Esc</kbd>\n }\n </button>\n <button\n (click)=\"downloadSession()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every registered entry, the event log, the cache totals and anything armed as one JSON file - the attachment for a bug report\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Session\n </button>\n <!-- A gear, not a tab: Settings holds nothing to count, so the badge/overflow logic would push it\n behind More - and the one control that explains a panel behaving oddly may not be hidden. -->\n <button\n [class.et-query-devtools-btn--active]=\"activeTab() === 'settings'\"\n [attr.aria-pressed]=\"activeTab() === 'settings'\"\n (click)=\"toggleSettings()\"\n class=\"et-query-devtools-btn et-query-devtools-icon-btn\"\n type=\"button\"\n title=\"Settings: where panel state is kept, and the limits it holds\"\n aria-label=\"Settings\"\n >\n <span aria-hidden=\"true\">\u2699</span>\n </button>\n @if (!poppedOut()) {\n @let layout = currentLayout();\n <div class=\"et-query-devtools-menu\" etMenu placement=\"bottom-end\">\n <button class=\"et-query-devtools-btn\" title=\"Where the panel sits\" type=\"button\" etMenuTrigger>\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">{{ layout.glyph }}</span>\n {{ layout.label }}\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <et-menu-radio-group [value]=\"dock()\">\n @for (option of LAYOUTS; track option.id) {\n <!-- eslint-disable ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <et-menu-radio-item\n [value]=\"option.id\"\n [title]=\"option.hint\"\n [closeOnActivate]=\"true\"\n (activate)=\"selectLayout(option.id)\"\n >\n <!-- eslint-enable ethlete/prefer-static-boolean-properties -->\n <span class=\"et-query-devtools-menu-item-glyph\" aria-hidden=\"true\">{{ option.glyph }}</span>\n {{ option.label }}\n </et-menu-radio-item>\n }\n </et-menu-radio-group>\n <et-menu-separator />\n <et-menu-checkbox-item\n [checked]=\"reservesSpace()\"\n [disabled]=\"floating()\"\n (checkedChange)=\"reservesSpace.set($event)\"\n title=\"The page gives up the room the panel covers, so its last rows can still be scrolled to. Docks only - a float sits wherever it was dragged.\"\n >\n Reserve page space\n </et-menu-checkbox-item>\n </et-menu>\n </ng-template>\n </div>\n }\n <span class=\"et-query-devtools-header-divider\" aria-hidden=\"true\"></span>\n <button\n (click)=\"toggleOpen()\"\n class=\"et-query-devtools-btn et-query-devtools-btn--danger\"\n type=\"button\"\n title=\"{{ poppedOut() ? 'Bring the panel back into the app window' : 'Close devtools (' + shortcut + ')' }}\"\n >\n @if (poppedOut()) {\n <span aria-hidden=\"true\">\u29C8</span>\n Dock back\n } @else {\n <span aria-hidden=\"true\">\u2715</span>\n Close\n <kbd class=\"et-query-devtools-kbd\">{{ shortcut }}</kbd>\n }\n </button>\n </div>\n </header>\n\n @if (popOutBlocked()) {\n <div class=\"et-query-devtools-notice-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-text\">\n The browser blocked the pop-up window. Allow pop-ups for this site, or float the panel over the page instead \u2014\n a float needs no permission.\n </span>\n <button (click)=\"floatPanel()\" class=\"et-query-devtools-btn\" type=\"button\">Float instead</button>\n <button (click)=\"popOutBlocked.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Dismiss</button>\n </div>\n }\n\n @if (armedFaultClients(); as armed) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Faults armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ armed.join(', ') }}</span>\n \u2014 every failure and delay you see from\n {{ armed.length === 1 ? 'this client' : 'these clients' }} is injected, not the API.\n @if (faultsRestored()) {\n They came back from your last page load \u2014 set <em>Armed faults</em> back to <em>none</em> in Settings to\n stop that.\n }\n </span>\n <button\n [disabled]=\"activeTab() === 'faults'\"\n (click)=\"activeTab.set('faults')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"CLEAR_FAULTS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n @if (armedMockRoutes(); as mocked) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Mocks armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ mocked.join(', ') }}</span>\n \u2014 {{ mocked.length === 1 ? 'that route is' : 'those routes are' }} answered by the panel, so nothing they show\n reached the API.\n @if (mocksRestored()) {\n They came back from your last page load \u2014 set <em>Armed mocks</em> back to <em>none</em> in Settings to stop\n that.\n }\n </span>\n <button\n [disabled]=\"activeTab() === 'mocks'\"\n (click)=\"activeTab.set('mocks')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"DISARM_MOCKS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n @if (restoredOverrides(); as restored) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n @if (restored.ops) {\n <strong>Overrides re-armed</strong> \u2014 {{ restored.ops }} {{ restored.ops === 1 ? 'edit' : 'edits' }} from\n your last page load {{ restored.ops === 1 ? 'is' : 'are' }} back on {{ restored.queries }}\n {{ restored.queries === 1 ? 'query' : 'queries' }}, so those responses are not what the API sent.\n @if (restored.fromLocalStorage) {\n They came back from <strong>localStorage</strong>, so they outlive closing this tab \u2014 clear them, or set\n overrides back to <em>session</em> in Settings.\n }\n } @else {\n <strong>Overrides could not be re-armed.</strong>\n }\n @if (restored.orphaned.length) {\n <span class=\"et-query-devtools-armed-orphans\">\n {{ restored.orphaned.length }} stored\n {{ restored.orphaned.length === 1 ? 'override' : 'overrides' }} matched no query \u2014\n <span class=\"et-query-devtools-mono\">{{ restored.orphaned.join(', ') }}</span>\n </span>\n }\n </span>\n @if (restored.firstId; as firstId) {\n <button (click)=\"reviewRestoredOverrides(firstId)\" class=\"et-query-devtools-btn\" type=\"button\">Review</button>\n }\n <button (click)=\"DROP_RESTORED_OVERRIDES()\" class=\"et-query-devtools-btn\" type=\"button\">Drop all</button>\n </div>\n }\n\n <div class=\"et-query-devtools-body\">\n @switch (activeTab()) {\n <!-- QUERIES -->\n @case ('queries') {\n <et-query-devtools-queries-tab />\n }\n\n <!-- STACKS -->\n @case ('stacks') {\n <et-query-devtools-stacks-tab />\n }\n\n <!-- SEQUENCES -->\n @case ('sequences') {\n <et-query-devtools-sequences-tab />\n }\n\n <!-- BATCHES -->\n @case ('batches') {\n <et-query-devtools-batches-tab />\n }\n\n <!-- FORMS -->\n @case ('forms') {\n <et-query-devtools-forms-tab />\n }\n\n <!-- AUTH -->\n @case ('auth') {\n <et-query-devtools-auth-tab />\n }\n\n <!-- SOCKETS -->\n @case ('ws') {\n <et-query-devtools-sockets-tab />\n }\n\n <!-- CACHE -->\n @case ('cache') {\n <et-query-devtools-cache-tab />\n }\n\n <!-- LOCKS -->\n @case ('locks') {\n <et-query-devtools-locks-tab />\n }\n\n <!-- TIMELINE -->\n @case ('timeline') {\n <et-query-devtools-timeline-tab />\n }\n\n <!-- EVENTS -->\n @case ('events') {\n <et-query-devtools-events-tab />\n }\n\n <!-- FAULTS -->\n @case ('faults') {\n <et-query-devtools-faults-tab />\n }\n\n <!-- MOCKS -->\n @case ('mocks') {\n <et-query-devtools-mocks-tab />\n }\n\n <!-- ABOUT -->\n @case ('about') {\n <et-query-devtools-about />\n }\n\n <!-- SETTINGS -->\n @case ('settings') {\n <et-query-devtools-settings />\n }\n }\n </div>\n </section>\n}\n\n@if (!open()) {\n <et-query-devtools-toggle [tampered]=\"panelTampered()\" (openChange)=\"toggleOpen()\" />\n}\n\n@if (inspectActive() && inspectHover(); as hover) {\n <div\n [style.left.px]=\"hover.rect.left\"\n [style.top.px]=\"hover.rect.top\"\n [style.width.px]=\"hover.rect.width\"\n [style.height.px]=\"hover.rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">{{ inspectLabel(hover.entries) }}</span>\n </div>\n}\n\n@if (locatedRect(); as rect) {\n <div\n [style.left.px]=\"rect.left\"\n [style.top.px]=\"rect.top\"\n [style.width.px]=\"rect.width\"\n [style.height.px]=\"rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">created here</span>\n </div>\n}\n", styles: ["@layer components{.et-query-devtools-host{--_et-qdt-bg: var(--et-surface-background-solid, rgb(24 24 27));--_et-qdt-fg: var(--et-surface-color-solid, rgb(244 244 245));--_et-qdt-muted: var(--et-surface-color-muted-solid, rgb(161 161 170));--_et-qdt-subtle: var(--et-surface-color-subtle-solid, rgb(113 113 122));--_et-qdt-border: var(--et-surface-border-solid, rgb(255 255 255 / .12));--_et-qdt-interaction: var(--et-surface-interaction-solid, rgb(255 255 255));--_et-qdt-line: color-mix(in srgb, var(--_et-qdt-interaction) 24%, transparent);--_et-qdt-line-strong: color-mix(in srgb, var(--_et-qdt-interaction) 42%, transparent);--_et-qdt-accent: var(--et-theme-color-primary-solid, rgb(96 165 250));--_et-qdt-hover: color-mix(in srgb, var(--_et-qdt-interaction) 10%, transparent);--_et-qdt-field: color-mix(in srgb, var(--_et-qdt-interaction) 8%, transparent);--_et-qdt-active: color-mix(in srgb, var(--_et-qdt-interaction) 18%, transparent);--_et-qdt-success: rgb(34 197 94);--_et-qdt-error: rgb(239 68 68);--_et-qdt-loading: rgb(234 179 8);--_et-qdt-title-bar: 2.4rem;--_et-qdt-radius-sm: .3rem;--_et-qdt-radius: .5rem;--_et-qdt-radius-lg: .6rem;--_et-qdt-control-h: 2.4rem;--_et-qdt-row-h: 3.6rem;position:fixed;inset-inline:0;inset-block-end:0;z-index:2147483010;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,Liberation Mono,monospace;font-size:1.2rem;line-height:1.4;pointer-events:none}.et-query-devtools-host>*{pointer-events:auto}@media(pointer:coarse){.et-query-devtools-host{--_et-qdt-control-h: 3.4rem;--_et-qdt-row-h: 4.4rem}}.et-query-devtools-panel{position:relative;display:flex;flex-direction:column;inline-size:100%;min-block-size:20rem;max-block-size:90vh;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border-block-start:1px solid var(--_et-qdt-border);box-shadow:0 -.4rem 2rem #00000059}.et-query-devtools-host:where([data-dock=right]){inset-block:0;inset-inline-start:auto}.et-query-devtools-panel:where([data-dock=right]){inline-size:auto;block-size:100%;max-block-size:none;max-inline-size:90vw;border-block-start:0;border-inline-start:1px solid var(--_et-qdt-border);box-shadow:-.4rem 0 2rem #00000059}.et-query-devtools-host:where([data-dock=float]){inset:0}.et-query-devtools-panel:where([data-dock=float]){--_et-qdt-frame: .8rem;--et-resize-handles-edge-size: var(--_et-qdt-frame);--et-resize-handles-touch-edge-size: var(--_et-qdt-frame);--et-resize-handles-corner-size: 1.6rem;--et-resize-handles-touch-corner-size: 1.6rem;--et-resize-handles-edge-inset: 1.6rem;--et-resize-handles-side-top: var(--_et-qdt-title-bar);--et-resize-handles-side-bottom: 1.6rem;--et-resize-handles-z-index: 3;position:absolute;inline-size:auto;min-block-size:0;max-block-size:none;padding-block-start:var(--_et-qdt-title-bar);padding-inline:var(--_et-qdt-frame);padding-block-end:var(--_et-qdt-frame);border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius-lg);box-shadow:0 1rem 4rem #00000073;overflow:hidden}.et-query-devtools-move{position:absolute;inset-inline:0;inset-block-start:0;block-size:var(--_et-qdt-title-bar);cursor:move;z-index:2}.et-query-devtools-panel:where([data-dock=float]) .et-query-devtools-body{border-radius:var(--_et-qdt-radius-sm);overflow:hidden}.et-query-devtools-move:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.9rem;inline-size:3rem;margin-inline-start:-1.5rem;block-size:.6rem;background-image:radial-gradient(currentcolor 40%,transparent 42%);background-size:.4rem .3rem;color:var(--_et-qdt-subtle)}@media(hover:hover){.et-query-devtools-move:hover:after{color:var(--_et-qdt-accent)}}.et-query-devtools-panel:where([data-dock=float][data-parked]){opacity:.85}@media(hover:hover){.et-query-devtools-panel:where([data-dock=float][data-parked]):hover{opacity:1}}.et-query-devtools-panel:where([data-dock=float][data-parked]) .et-query-devtools-move{cursor:grab;background:var(--_et-qdt-active)}.et-query-devtools-panel:where([data-dock=float][data-parked]) .et-query-devtools-move:after{color:var(--_et-qdt-accent)}.et-query-devtools-host:where([data-dock=left]){inset-block:0;inset-inline-end:auto}.et-query-devtools-panel:where([data-dock=left]){inline-size:auto;block-size:100%;max-block-size:none;max-inline-size:90vw;border-block-start:0;border-inline-end:1px solid var(--_et-qdt-border);box-shadow:.4rem 0 2rem #00000059}.et-query-devtools-host:where([data-dock=top]){inset-block-start:0;inset-block-end:auto}.et-query-devtools-panel:where([data-dock=top]){border-block-start:0;border-block-end:1px solid var(--_et-qdt-border);box-shadow:0 .4rem 2rem #00000059}.et-query-devtools-host:where([data-dock=popout]){position:static;inline-size:100%;block-size:100%}.et-query-devtools-panel:where([data-dock=popout]){block-size:100%;max-block-size:none;border:0;box-shadow:none}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-header{flex-wrap:wrap}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-tabs,.et-query-devtools-header-actions){flex:1 1 auto;flex-wrap:nowrap;min-inline-size:0;overflow-x:auto}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-tabs,.et-query-devtools-header-actions)>*{flex:none}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-master-detail,.et-query-devtools-split){flex-direction:column}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-list,.et-query-devtools-detail,.et-query-devtools-drawer,.et-query-devtools-split .et-query-devtools-scroll){flex:1 1 50%;min-inline-size:0;min-block-size:0;max-inline-size:none;border-inline-start:0;border-inline-end:0}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-detail,.et-query-devtools-drawer){border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-master-detail>.et-query-devtools-list{flex:0 1 var(--_et-qdt-list-height, 50%);min-block-size:var(--_et-qdt-list-height, 0);max-block-size:var(--_et-qdt-list-height, none)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-split .et-query-devtools-drawer{flex:0 1 var(--_et-qdt-drawer-height, 50%);min-block-size:var(--_et-qdt-drawer-height, 0);max-block-size:var(--_et-qdt-drawer-height, none)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-pane-resize{cursor:ns-resize}.et-query-devtools-panel--resizing{-webkit-user-select:none;user-select:none}.et-query-devtools-resize,.et-query-devtools-pane-resize{touch-action:none}.et-query-devtools-resize{position:absolute;inset-inline:0;inset-block-start:0;block-size:.6rem;cursor:ns-resize;z-index:1}.et-query-devtools-panel:where([data-dock=right]) .et-query-devtools-resize{inset-inline:0 auto;inset-block:0;inline-size:.6rem;block-size:auto;cursor:ew-resize}.et-query-devtools-panel:where([data-dock=left]) .et-query-devtools-resize{inset-inline:auto 0;inset-block:0;inline-size:.6rem;block-size:auto;cursor:ew-resize}.et-query-devtools-panel:where([data-dock=top]) .et-query-devtools-resize{inset-block:auto 0}.et-query-devtools-resize:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.2rem;inline-size:4rem;margin-inline-start:-2rem;block-size:.2rem;border-radius:999px;background:var(--_et-qdt-border)}.et-query-devtools-panel:where([data-dock=right]) .et-query-devtools-resize:after{inset-inline:.2rem auto;inset-block:50% auto;inline-size:.2rem;block-size:4rem;margin-inline-start:0;margin-block-start:-2rem}.et-query-devtools-panel:where([data-dock=left]) .et-query-devtools-resize:after{inset-inline:auto .2rem;inset-block:50% auto;inline-size:.2rem;block-size:4rem;margin-inline-start:0;margin-block-start:-2rem}.et-query-devtools-panel:where([data-dock=top]) .et-query-devtools-resize:after{inset-block:auto .2rem}@media(hover:hover){.et-query-devtools-resize:hover:after{background:var(--_et-qdt-accent)}}.et-query-devtools-header{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding-inline:.8rem;border-block-end:1px solid var(--_et-qdt-border);flex:0 0 auto}.et-query-devtools-armed-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-block-end:1px solid color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-armed-text{flex:1 1 auto;min-inline-size:0}.et-query-devtools-notice-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:var(--_et-qdt-hover);border-block-end:1px solid var(--_et-qdt-border);color:var(--_et-qdt-fg);font-size:1.1rem}.et-query-devtools-notice-bar .et-query-devtools-btn{flex:0 0 auto}.et-query-devtools-armed-orphans{display:block;opacity:.85}.et-query-devtools-armed-dot{flex:0 0 auto;inline-size:.8rem;block-size:.8rem;border-radius:50%;background:var(--_et-qdt-error);animation:et-query-devtools-armed-pulse 1.6s ease-in-out infinite}@media(prefers-reduced-motion:reduce){.et-query-devtools-armed-dot{animation:none}}@keyframes et-query-devtools-armed-pulse{50%{opacity:.25}}.et-query-devtools-armed-bar .et-query-devtools-btn{flex:0 0 auto;border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-tabs{display:flex;flex-wrap:wrap;gap:.2rem}.et-query-devtools-tab{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:none;background:transparent;color:var(--_et-qdt-muted);padding:.9rem 1rem;cursor:pointer;border-block-end:2px solid transparent;font:inherit}@media(hover:hover){.et-query-devtools-tab:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-tab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-tab-badge{display:inline-block;padding-inline:.45rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-interaction) 12%,transparent);color:var(--_et-qdt-muted);font-size:1rem;font-variant-numeric:tabular-nums}.et-query-devtools-tab-badge--error{background:color-mix(in srgb,var(--_et-qdt-error) 25%,transparent);color:var(--_et-qdt-fg)}.et-query-devtools-menu{display:contents}.et-query-devtools-menu-caret{font-size:.9em;opacity:.7}.et-query-devtools-menu-glyph{display:inline-block;min-inline-size:1.2rem;text-align:center}.et-query-devtools-menu>[data-menu-open]{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}.et-menu .et-query-devtools-menu-item-glyph{display:inline-block;inline-size:16px;text-align:center}.et-menu-item-content:has(.et-query-devtools-menu-item-note){display:flex;flex-direction:column;align-items:start;gap:2px;padding-block:8px}.et-query-devtools-menu-item-note{color:var(--et-surface-color-muted-solid, rgb(255 255 255 / .55));font-size:12px}.et-query-devtools-header-actions{display:flex;align-items:center;gap:.2rem;flex:0 0 auto}.et-query-devtools-header-divider{inline-size:1px;block-size:1.6rem;margin-inline:.4rem;background:var(--_et-qdt-border)}.et-query-devtools-inspect-btn{position:relative}.et-query-devtools-inspect-icon{inline-size:1.4rem;block-size:1.4rem;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.25;stroke-linecap:round}.et-query-devtools-inspect-icon-dot{fill:currentColor;stroke:none}.et-query-devtools-btn.et-query-devtools-inspect-btn--active,.et-query-devtools-btn.et-query-devtools-inspect-btn--active:hover{color:#09090b;background:var(--_et-qdt-accent);border-color:var(--_et-qdt-accent)}.et-query-devtools-inspect-btn--active:after{content:\"\";position:absolute;inset:-.3rem;border:1px solid var(--_et-qdt-accent);border-radius:calc(var(--_et-qdt-radius) + .3rem);pointer-events:none;animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}@keyframes et-query-devtools-inspect-pulse{0%,to{opacity:.2}50%{opacity:1}}@media(prefers-reduced-motion:reduce){.et-query-devtools-inspect-btn--active:after,.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:none;opacity:1}}.et-query-devtools-kbd{border-radius:var(--_et-qdt-radius-sm);background:var(--_et-qdt-hover);box-shadow:inset 0 0 0 1px var(--_et-qdt-border);color:var(--_et-qdt-muted);padding:.15rem .4rem;font:inherit;font-size:1.1rem;line-height:1.2;white-space:nowrap}.et-query-devtools-kbd--inverse{background:#00000026;box-shadow:inset 0 0 0 1px #00000040;color:inherit}@media(hover:none)and (pointer:coarse){.et-query-devtools-kbd{display:none}}.et-query-devtools-inspect-box{position:fixed;z-index:2147483002;pointer-events:none;border:2px solid var(--_et-qdt-accent);background:color-mix(in srgb,var(--_et-qdt-accent) 15%,transparent);border-radius:var(--_et-qdt-radius-sm)}.et-query-devtools-inspect-tag{position:absolute;inset-block-start:100%;inset-inline-start:0;margin-block-start:.2rem;padding:.2rem .5rem;border-radius:var(--_et-qdt-radius-sm);background:var(--_et-qdt-accent);color:#09090b;font-family:ui-monospace,monospace;font-size:1.1rem;font-weight:600;white-space:nowrap}.et-query-devtools-panel :is(a,button,input,select,summary,textarea,[tabindex]):focus-visible{outline:2px solid var(--_et-qdt-accent);outline-offset:1px}.et-query-devtools-body{flex:1 1 auto;min-block-size:0;overflow:hidden}.et-query-devtools-detail{flex:1 1 auto;min-inline-size:0;overflow-y:auto;padding:1rem}.et-query-devtools-detail .et-query-devtools-meta,.et-query-devtools-drawer .et-query-devtools-meta{padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-detail .et-query-devtools-section-row,.et-query-devtools-drawer .et-query-devtools-section-row{padding-block-start:.8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-filter-chip{display:inline-flex;align-items:center;gap:.6rem;padding:.3rem .35rem .3rem .7rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_et-qdt-accent) 45%,transparent);color:var(--_et-qdt-accent);font-size:1.1rem;font-weight:600;letter-spacing:.02em}.et-query-devtools-filter-icon{font-size:1.3rem;line-height:1}.et-query-devtools-filter-count{border-radius:999px;background:var(--_et-qdt-accent);color:#09090b;min-inline-size:1.8rem;padding:0 .5rem;text-align:center}.et-query-devtools-filter-clear{appearance:none;display:inline-flex;align-items:center;gap:.5rem;min-block-size:var(--_et-qdt-control-h);border:1px solid var(--_et-qdt-line);border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-fg);cursor:pointer;padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-filter-clear:hover{border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent);color:var(--_et-qdt-fg)}}.et-query-devtools-scroll{block-size:100%;overflow:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.et-query-devtools-split{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-split .et-query-devtools-scroll{flex:1 1 auto;min-inline-size:0}.et-query-devtools-pane{display:flex;flex-direction:column;flex:1 1 auto;block-size:100%;min-inline-size:0;min-block-size:0;overflow:hidden}.et-query-devtools-pane>.et-query-devtools-scroll{padding-block:0 1rem}.et-query-devtools-pane-resize{flex:0 0 .5rem;align-self:stretch;cursor:ew-resize;background:transparent;transition:background .12s ease}.et-query-devtools-pane-resize:active{background:var(--_et-qdt-accent)}@media(hover:hover){.et-query-devtools-pane-resize:hover{background:var(--_et-qdt-accent)}}.et-query-devtools-toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem;padding:.8rem;position:sticky;inset-block-start:0;z-index:1;background:var(--_et-qdt-bg);border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-scroll>.et-query-devtools-toolbar{inset-block-start:-1rem}.et-query-devtools-toolbar-row{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 100%;gap:.5rem}.et-query-devtools-field{display:inline-flex;align-items:center;gap:.6rem;color:var(--_et-qdt-muted)}.et-query-devtools-select{flex:0 0 auto;min-block-size:var(--_et-qdt-control-h);background:color-mix(in srgb,var(--_et-qdt-interaction) 8%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .8rem;font:inherit;font-size:1.15rem}.et-query-devtools-select:focus-visible{border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent)}.et-query-devtools-select option{background-color:var(--_et-qdt-bg);color:var(--_et-qdt-fg)}.et-query-devtools-count{color:var(--_et-qdt-subtle)}.et-query-devtools-row{display:flex;align-items:stretch;inline-size:100%;min-block-size:var(--_et-qdt-row-h);border-block-end:1px solid var(--_et-qdt-border);color:var(--_et-qdt-fg)}@media(hover:hover){.et-query-devtools-row:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-method{font-weight:700;font-size:1.1rem;color:var(--_et-qdt-muted);white-space:nowrap}.et-query-devtools-route{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1 1 auto;min-inline-size:0}.et-query-devtools-status{inline-size:.8rem;block-size:.8rem;border-radius:999px;flex:0 0 auto;background:var(--_et-qdt-subtle)}.et-query-devtools-status[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-status[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-status[data-status=loading],.et-query-devtools-status[data-status=running]{background:var(--_et-qdt-loading)}.et-query-devtools-btn{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:.5rem;flex:0 0 auto;min-block-size:var(--_et-qdt-control-h);border:1px solid var(--_et-qdt-line);border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-fg);padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4;white-space:nowrap;cursor:pointer;transition:color .12s ease,background-color .12s ease,border-color .12s ease}@media(hover:hover){.et-query-devtools-btn:hover:not(:disabled){background:var(--_et-qdt-hover);border-color:var(--_et-qdt-line-strong);color:var(--_et-qdt-fg)}}.et-query-devtools-btn:disabled{border-color:var(--_et-qdt-border);color:var(--_et-qdt-subtle);cursor:default;background:transparent;opacity:.6}.et-query-devtools-btn--primary,.et-query-devtools-btn--primary:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 40%,transparent);color:var(--_et-qdt-accent)}@media(hover:hover){.et-query-devtools-btn--primary:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-accent) 26%,transparent)}}.et-query-devtools-btn--danger{border-color:color-mix(in srgb,var(--_et-qdt-error) 32%,transparent);color:color-mix(in srgb,var(--_et-qdt-error) 55%,var(--_et-qdt-fg))}@media(hover:hover){.et-query-devtools-btn--danger:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-error) 16%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);color:var(--_et-qdt-error)}}.et-query-devtools-btn--tampered,.et-query-devtools-btn--tampered:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-icon-btn{inline-size:var(--_et-qdt-control-h);padding-inline:0;font-size:1.3rem}.et-query-devtools-btn-group{display:inline-flex;flex:0 0 auto;border:1px solid var(--_et-qdt-line);border-radius:var(--_et-qdt-radius);overflow:hidden}.et-query-devtools-btn-group>.et-query-devtools-btn{border-radius:0;border-block:0;border-inline-start:0;border-inline-end:1px solid var(--_et-qdt-line)}.et-query-devtools-btn-group>.et-query-devtools-btn:last-child{border-inline-end:0}.et-query-devtools-btn-group>.et-query-devtools-btn:focus-visible{outline-offset:-2px}.et-query-devtools-toolbar-btn{margin-inline-start:auto;flex:0 0 auto}.et-query-devtools-meta{display:grid;grid-template-columns:auto 1fr;gap:.3rem 1rem;margin:0 0 1rem}.et-query-devtools-meta dt{color:var(--_et-qdt-muted)}.et-query-devtools-meta dd{margin:0;overflow-wrap:anywhere}.et-query-devtools-section{margin:1rem 0 .4rem;font-size:1.1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat{padding:.6rem .8rem;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-stat dt{font-size:1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat dd{margin:0;font-size:1.5rem;font-variant-numeric:tabular-nums}.et-query-devtools-stat-sub{display:block;font-size:1rem;color:var(--_et-qdt-subtle)}.et-query-devtools-json{margin:0;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow-x:auto;white-space:pre-wrap;overflow-wrap:anywhere;max-block-size:24rem;overflow-y:auto}.et-query-devtools-json--error{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-mono{font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.et-query-devtools-url{display:inline-block;max-inline-size:32rem;margin-inline-start:.6rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.et-query-devtools-chips,.et-query-devtools-chip-row{display:flex;flex-wrap:wrap;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-chip{display:inline-flex;align-items:center;gap:.3rem;padding:.2rem .6rem;border-radius:999px;border:1px solid var(--_et-qdt-border);font-size:1.1rem;color:var(--_et-qdt-fg);background:color-mix(in srgb,var(--_et-qdt-interaction) 6%,transparent)}.et-query-devtools-chip--muted{color:var(--_et-qdt-muted)}.et-query-devtools-retry-glyph{font-size:.7em;line-height:1}.et-query-devtools-chip--loading{color:var(--_et-qdt-loading);border-color:color-mix(in srgb,var(--_et-qdt-loading) 50%,transparent)}.et-query-devtools-chip--error{color:var(--_et-qdt-error);border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-chip--success{color:var(--_et-qdt-success);border-color:color-mix(in srgb,var(--_et-qdt-success) 50%,transparent)}button.et-query-devtools-chip--button{appearance:none;border-color:var(--_et-qdt-line);cursor:pointer;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}@media(hover:hover){button.et-query-devtools-chip--button:hover{border-color:var(--_et-qdt-accent);color:var(--_et-qdt-accent)}}.et-query-devtools-facet{appearance:none;border-color:var(--_et-qdt-line);min-block-size:var(--_et-qdt-control-h);padding-inline:.8rem;background:transparent;cursor:pointer;color:var(--_et-qdt-muted);font:inherit;font-size:1.1rem;line-height:1.4}@media(hover:hover){.et-query-devtools-facet:hover:not(:disabled){color:var(--_et-qdt-fg);background:var(--_et-qdt-hover);border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent)}}.et-query-devtools-facet:disabled{color:var(--_et-qdt-subtle);cursor:default}.et-query-devtools-facet--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 14%,transparent)}.et-query-devtools-facet-count{font-variant-numeric:tabular-nums;color:inherit;opacity:.75}.et-query-devtools-card{border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius-lg);padding:1rem}.et-query-devtools-card--armed{border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-card-head{display:flex;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-inner-queries{display:flex;flex-direction:column;gap:.3rem}.et-query-devtools-inner-row{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-fg);padding:.4rem .6rem;cursor:pointer;font:inherit}@media(hover:hover){.et-query-devtools-inner-row:hover:not([disabled]){background:var(--_et-qdt-hover)}}.et-query-devtools-inner-row--active{background:var(--_et-qdt-active);border-color:var(--_et-qdt-accent)}.et-query-devtools-inner-row[disabled]{cursor:default;opacity:.6}.et-query-devtools-step-toggle{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:none;background:transparent;color:var(--_et-qdt-fg);padding:.6rem;cursor:pointer;font:inherit}@media(hover:hover){.et-query-devtools-step-toggle:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-step-index{flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret{color:var(--_et-qdt-muted)}.et-query-devtools-table{inline-size:100%;border-collapse:collapse}.et-query-devtools-table-scroll{overflow-x:auto}.et-query-devtools-table-scroll>.et-query-devtools-table{min-inline-size:max-content}.et-query-devtools-table th,.et-query-devtools-table td{text-align:start;padding:.5rem .6rem;border-block-end:1px solid var(--_et-qdt-border);vertical-align:top}.et-query-devtools-table td .et-query-devtools-chip{padding-block:0;line-height:1.4}.et-query-devtools-table th{color:var(--_et-qdt-muted);font-weight:600;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-table tr[data-status=error] td{color:var(--_et-qdt-error)}.et-query-devtools-table .et-query-devtools-cell-origin{inline-size:12ch;max-inline-size:12ch;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;overflow-wrap:normal}.et-query-devtools-empty{color:var(--_et-qdt-subtle);padding:1.2rem;text-align:center}.et-query-devtools-cell-actions{display:inline-flex;gap:.4rem;white-space:nowrap}.et-query-devtools-section-row{display:flex;align-items:center;justify-content:space-between;gap:.8rem;margin-block-start:1rem}.et-query-devtools-search{flex:1 1 16rem;min-inline-size:0;min-block-size:var(--_et-qdt-control-h);background:var(--_et-qdt-field);color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .8rem;font:inherit;font-size:1.15rem}.et-query-devtools-search:focus{outline:none;border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:var(--_et-qdt-bg)}.et-query-devtools-row-link{display:inline-flex;align-items:center;gap:.6rem;appearance:none;border:0;background:none;padding:0;color:inherit;font:inherit;text-align:start;cursor:pointer}.et-query-devtools-row-link:focus-visible .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}@media(hover:hover){.et-query-devtools-row-link:hover .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}}.et-query-devtools-hint{flex:1 1 24rem;color:var(--_et-qdt-muted);font-size:1.1rem;margin:0}.et-query-devtools-input{inline-size:100%;min-block-size:var(--_et-qdt-control-h);background:var(--_et-qdt-field);color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .8rem;font:inherit;font-size:1.15rem}.et-query-devtools-input:focus{outline:none;border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:var(--_et-qdt-bg)}.et-query-devtools-input--number{inline-size:8rem;font-variant-numeric:tabular-nums}.et-query-devtools-editor{border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);padding:.8rem;margin-block-end:1rem}.et-query-devtools-editor-error{color:var(--_et-qdt-error);margin:.4rem 0 0}.et-query-devtools-btn--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsAuthTabComponent, selector: "et-query-devtools-auth-tab" }, { kind: "component", type: QueryDevtoolsBatchesTabComponent, selector: "et-query-devtools-batches-tab" }, { kind: "component", type: QueryDevtoolsCacheTabComponent, selector: "et-query-devtools-cache-tab" }, { kind: "component", type: QueryDevtoolsEventsTabComponent, selector: "et-query-devtools-events-tab" }, { kind: "component", type: QueryDevtoolsAboutComponent, selector: "et-query-devtools-about" }, { kind: "component", type: QueryDevtoolsSettingsComponent, selector: "et-query-devtools-settings" }, { kind: "component", type: QueryDevtoolsFaultsTabComponent, selector: "et-query-devtools-faults-tab" }, { kind: "component", type: QueryDevtoolsFormsTabComponent, selector: "et-query-devtools-forms-tab" }, { kind: "component", type: QueryDevtoolsLocksTabComponent, selector: "et-query-devtools-locks-tab" }, { kind: "component", type: QueryDevtoolsMocksTabComponent, selector: "et-query-devtools-mocks-tab" }, { kind: "component", type: QueryDevtoolsQueriesTabComponent, selector: "et-query-devtools-queries-tab" }, { kind: "component", type: QueryDevtoolsSequencesTabComponent, selector: "et-query-devtools-sequences-tab" }, { kind: "component", type: QueryDevtoolsSocketsTabComponent, selector: "et-query-devtools-sockets-tab" }, { kind: "component", type: QueryDevtoolsStacksTabComponent, selector: "et-query-devtools-stacks-tab" }, { kind: "component", type: QueryDevtoolsTimelineTabComponent, selector: "et-query-devtools-timeline-tab" }, { kind: "component", type: QueryDevtoolsToggleComponent, selector: "et-query-devtools-toggle", inputs: ["tampered"], outputs: ["openChange"] }, { kind: "directive", type: DragHandleDirective, selector: "[etDragHandle]", inputs: ["commitThreshold", "disabled"], outputs: ["dragTapped", "dragStarted", "dragMoved", "dragEnded", "dragCancelled"] }, { kind: "component", type: MenuCheckboxItemComponent, selector: "et-menu-checkbox-item", inputs: ["icon"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "component", type: MenuRadioGroupComponent, selector: "et-menu-radio-group" }, { kind: "component", type: MenuRadioItemComponent, selector: "et-menu-radio-item", inputs: ["icon"] }, { kind: "component", type: MenuSeparatorComponent, selector: "et-menu-separator" }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "component", type: ResizeHandlesComponent, selector: "et-resize-handles", inputs: ["edges", "disabled"], outputs: ["resizeStarted", "resizeMoved", "resizeEnded", "resizeCancelled"] }], encapsulation: i0.ViewEncapsulation.None }); }
7851
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7852
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: QueryDevtoolsComponent, isStandalone: true, selector: "et-query-devtools", inputs: { startOpen: { classPropertyName: "startOpen", publicName: "startOpen", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-et-overlay-layer": "2147483020" }, properties: { "attr.data-dock": "dock()" }, classAttribute: "et-query-devtools-host" }, providers: [{ provide: QUERY_DEVTOOLS_HOST, useExisting: QueryDevtoolsComponent }], viewQueries: [{ propertyName: "panelEl", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (open()) {\n <section\n #panel\n [class.et-query-devtools-panel--resizing]=\"resizing()\"\n [attr.data-dock]=\"poppedOut() ? 'popout' : dock()\"\n [attr.data-pane-axis]=\"paneAxis()\"\n [attr.data-parked]=\"floatParked() ? '' : null\"\n [style.block-size.px]=\"panelBlockSize()\"\n [style.inline-size.px]=\"panelInlineSize()\"\n [style.inset-block-start.px]=\"panelInsetBlock()\"\n [style.inset-inline-start.px]=\"panelInsetInline()\"\n [style.--_et-qdt-list-width.px]=\"listWidth()\"\n [style.--_et-qdt-drawer-width.px]=\"drawerWidth()\"\n [style.--_et-qdt-list-height.px]=\"listHeight()\"\n [style.--_et-qdt-drawer-height.px]=\"drawerHeight()\"\n class=\"et-query-devtools-panel\"\n >\n <!-- A pop-out is sized by its window, so there is no edge to drag. A float is a window in the page:\n a title bar to move it and every edge to resize it, on the same `@ethlete/core` primitives the\n stream pip window uses. A dock has one edge, and it does both. -->\n @if (floating()) {\n <!-- Parked, the panel is a grab strip: the edge handles would eat most of the peek that is left\n to click, and resizing something you cannot see is not a thing to offer. -->\n <et-resize-handles\n [edges]=\"FLOAT_RESIZE_EDGES\"\n [disabled]=\"floatParked()\"\n (resizeStarted)=\"startFloatResize()\"\n (resizeMoved)=\"resizeFloat($event)\"\n (resizeEnded)=\"endFloatResize()\"\n (resizeCancelled)=\"endFloatResize()\"\n />\n <div\n (dragMoved)=\"moveFloat($event)\"\n (dragEnded)=\"endFloatMove()\"\n (dragCancelled)=\"endFloatMove()\"\n (dragTapped)=\"restoreFloat()\"\n class=\"et-query-devtools-move\"\n title=\"Drag to move \u2014 shove it off an edge to park it, click the strip to bring it back\"\n aria-hidden=\"true\"\n etDragHandle\n ></div>\n } @else if (!poppedOut()) {\n <div\n (pointerdown)=\"startResize($event)\"\n class=\"et-query-devtools-resize\"\n title=\"Drag to resize\"\n aria-hidden=\"true\"\n ></div>\n }\n <header class=\"et-query-devtools-header\">\n <nav class=\"et-query-devtools-tabs\" role=\"tablist\">\n @for (tab of visibleTabs(); track tab.id) {\n @let badge = tabBadges()[tab.id];\n <button\n [class.et-query-devtools-tab--active]=\"activeTab() === tab.id\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n (click)=\"activeTab.set(tab.id)\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (badge.count) {\n <span class=\"et-query-devtools-tab-badge\">{{ badge.count }}</span>\n }\n @if (badge.errors) {\n <span\n class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\"\n title=\"{{ badge.errors }} {{ badge.errorNoun ?? 'failing' }}\"\n >\n {{ badge.errors }}\n </span>\n }\n </button>\n }\n\n @if (overflowTabs().length; as overflowCount) {\n <div class=\"et-query-devtools-menu\" etMenu>\n <button\n [title]=\"overflowCount + ' tabs with nothing in them'\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n etMenuTrigger\n >\n More\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n @for (tab of overflowTabs(); track tab.id) {\n <button (activate)=\"activeTab.set(tab.id)\" et-menu-item type=\"button\">{{ tab.label }}</button>\n }\n </et-menu>\n </ng-template>\n </div>\n }\n </nav>\n\n <div class=\"et-query-devtools-header-actions\">\n <button\n [class.et-query-devtools-inspect-btn--active]=\"inspectActive()\"\n [attr.aria-pressed]=\"inspectActive()\"\n (click)=\"toggleInspect()\"\n class=\"et-query-devtools-btn et-query-devtools-inspect-btn\"\n type=\"button\"\n title=\"Inspect: hover the UI to find its query\"\n >\n <svg\n class=\"et-query-devtools-inspect-icon\"\n viewBox=\"0 0 16 16\"\n width=\"14\"\n height=\"14\"\n fill=\"none\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <circle class=\"et-query-devtools-inspect-icon-ring\" cx=\"8\" cy=\"8\" r=\"4.25\" />\n <circle class=\"et-query-devtools-inspect-icon-dot\" cx=\"8\" cy=\"8\" r=\"1.15\" />\n <path d=\"M8 0.9v2.1M8 13v2.1M0.9 8h2.1M13 8h2.1\" />\n </svg>\n {{ inspectActive() ? 'Inspecting' : 'Inspect' }}\n @if (inspectActive()) {\n <kbd class=\"et-query-devtools-kbd et-query-devtools-kbd--inverse\">Esc</kbd>\n }\n </button>\n <button\n (click)=\"downloadSession()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every registered entry, the event log, the cache totals and anything armed as one JSON file - the attachment for a bug report\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Session\n </button>\n <!-- A gear, not a tab: Settings holds nothing to count, so the badge/overflow logic would push it\n behind More - and the one control that explains a panel behaving oddly may not be hidden. -->\n <button\n [class.et-query-devtools-btn--active]=\"activeTab() === 'settings'\"\n [attr.aria-pressed]=\"activeTab() === 'settings'\"\n (click)=\"toggleSettings()\"\n class=\"et-query-devtools-btn et-query-devtools-icon-btn\"\n type=\"button\"\n title=\"Settings: where panel state is kept, and the limits it holds\"\n aria-label=\"Settings\"\n >\n <span aria-hidden=\"true\">\u2699</span>\n </button>\n @if (!poppedOut()) {\n @let layout = currentLayout();\n <div class=\"et-query-devtools-menu\" etMenu placement=\"bottom-end\">\n <button class=\"et-query-devtools-btn\" title=\"Where the panel sits\" type=\"button\" etMenuTrigger>\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">{{ layout.glyph }}</span>\n {{ layout.label }}\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <et-menu-radio-group [value]=\"dock()\">\n @for (option of LAYOUTS; track option.id) {\n <!-- eslint-disable ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <et-menu-radio-item\n [value]=\"option.id\"\n [title]=\"option.hint\"\n [closeOnActivate]=\"true\"\n (activate)=\"selectLayout(option.id)\"\n >\n <!-- eslint-enable ethlete/prefer-static-boolean-properties -->\n <span class=\"et-query-devtools-menu-item-glyph\" aria-hidden=\"true\">{{ option.glyph }}</span>\n {{ option.label }}\n </et-menu-radio-item>\n }\n </et-menu-radio-group>\n <et-menu-separator />\n <et-menu-checkbox-item\n [checked]=\"reservesSpace()\"\n [disabled]=\"floating()\"\n (checkedChange)=\"reservesSpace.set($event)\"\n title=\"The page gives up the room the panel covers, so its last rows can still be scrolled to. Docks only - a float sits wherever it was dragged.\"\n >\n Reserve page space\n </et-menu-checkbox-item>\n </et-menu>\n </ng-template>\n </div>\n }\n <span class=\"et-query-devtools-header-divider\" aria-hidden=\"true\"></span>\n <button\n (click)=\"toggleOpen()\"\n class=\"et-query-devtools-btn et-query-devtools-btn--danger\"\n type=\"button\"\n title=\"{{ poppedOut() ? 'Bring the panel back into the app window' : 'Close devtools (' + shortcut + ')' }}\"\n >\n @if (poppedOut()) {\n <span aria-hidden=\"true\">\u29C8</span>\n Dock back\n } @else {\n <span aria-hidden=\"true\">\u2715</span>\n Close\n <kbd class=\"et-query-devtools-kbd\">{{ shortcut }}</kbd>\n }\n </button>\n </div>\n </header>\n\n @if (popOutBlocked()) {\n <div class=\"et-query-devtools-notice-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-text\">\n The browser blocked the pop-up window. Allow pop-ups for this site, or float the panel over the page instead \u2014\n a float needs no permission.\n </span>\n <button (click)=\"floatPanel()\" class=\"et-query-devtools-btn\" type=\"button\">Float instead</button>\n <button (click)=\"popOutBlocked.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Dismiss</button>\n </div>\n }\n\n @if (armedFaultClients(); as armed) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Faults armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ armed.join(', ') }}</span>\n \u2014 every failure and delay you see from\n {{ armed.length === 1 ? 'this client' : 'these clients' }} is injected, not the API.\n @if (faultsRestored()) {\n They came back from your last page load \u2014 set <em>Armed faults</em> back to <em>none</em> in Settings to\n stop that.\n }\n </span>\n <button\n [disabled]=\"activeTab() === 'faults'\"\n (click)=\"activeTab.set('faults')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"CLEAR_FAULTS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n @if (armedMockRoutes(); as mocked) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Mocks armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ mocked.join(', ') }}</span>\n \u2014 {{ mocked.length === 1 ? 'that route is' : 'those routes are' }} answered by the panel, so nothing they show\n reached the API.\n @if (mocksRestored()) {\n They came back from your last page load \u2014 set <em>Armed mocks</em> back to <em>none</em> in Settings to stop\n that.\n }\n </span>\n <button\n [disabled]=\"activeTab() === 'mocks'\"\n (click)=\"activeTab.set('mocks')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"DISARM_MOCKS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n @if (restoredOverrides(); as restored) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n @if (restored.ops) {\n <strong>Overrides re-armed</strong> \u2014 {{ restored.ops }} {{ restored.ops === 1 ? 'edit' : 'edits' }} from\n your last page load {{ restored.ops === 1 ? 'is' : 'are' }} back on {{ restored.queries }}\n {{ restored.queries === 1 ? 'query' : 'queries' }}, so those responses are not what the API sent.\n @if (restored.fromLocalStorage) {\n They came back from <strong>localStorage</strong>, so they outlive closing this tab \u2014 clear them, or set\n overrides back to <em>session</em> in Settings.\n }\n } @else {\n <strong>Overrides could not be re-armed.</strong>\n }\n @if (restored.orphaned.length) {\n <span class=\"et-query-devtools-armed-orphans\">\n {{ restored.orphaned.length }} stored\n {{ restored.orphaned.length === 1 ? 'override' : 'overrides' }} matched no query \u2014\n <span class=\"et-query-devtools-mono\">{{ restored.orphaned.join(', ') }}</span>\n </span>\n }\n </span>\n @if (restored.firstId; as firstId) {\n <button (click)=\"reviewRestoredOverrides(firstId)\" class=\"et-query-devtools-btn\" type=\"button\">Review</button>\n }\n <button (click)=\"DROP_RESTORED_OVERRIDES()\" class=\"et-query-devtools-btn\" type=\"button\">Drop all</button>\n </div>\n }\n\n <div class=\"et-query-devtools-body\">\n @switch (activeTab()) {\n <!-- QUERIES -->\n @case ('queries') {\n <et-query-devtools-queries-tab />\n }\n\n <!-- STACKS -->\n @case ('stacks') {\n <et-query-devtools-stacks-tab />\n }\n\n <!-- SEQUENCES -->\n @case ('sequences') {\n <et-query-devtools-sequences-tab />\n }\n\n <!-- BATCHES -->\n @case ('batches') {\n <et-query-devtools-batches-tab />\n }\n\n <!-- FORMS -->\n @case ('forms') {\n <et-query-devtools-forms-tab />\n }\n\n <!-- AUTH -->\n @case ('auth') {\n <et-query-devtools-auth-tab />\n }\n\n <!-- SOCKETS -->\n @case ('ws') {\n <et-query-devtools-sockets-tab />\n }\n\n <!-- CACHE -->\n @case ('cache') {\n <et-query-devtools-cache-tab />\n }\n\n <!-- LOCKS -->\n @case ('locks') {\n <et-query-devtools-locks-tab />\n }\n\n <!-- TIMELINE -->\n @case ('timeline') {\n <et-query-devtools-timeline-tab />\n }\n\n <!-- EVENTS -->\n @case ('events') {\n <et-query-devtools-events-tab />\n }\n\n <!-- FAULTS -->\n @case ('faults') {\n <et-query-devtools-faults-tab />\n }\n\n <!-- MOCKS -->\n @case ('mocks') {\n <et-query-devtools-mocks-tab />\n }\n\n <!-- ABOUT -->\n @case ('about') {\n <et-query-devtools-about />\n }\n\n <!-- SETTINGS -->\n @case ('settings') {\n <et-query-devtools-settings />\n }\n }\n </div>\n </section>\n}\n\n@if (!open()) {\n <et-query-devtools-toggle [tampered]=\"panelTampered()\" (openChange)=\"toggleOpen()\" />\n}\n\n@if (inspectActive() && inspectHover(); as hover) {\n <div\n [style.left.px]=\"hover.rect.left\"\n [style.top.px]=\"hover.rect.top\"\n [style.width.px]=\"hover.rect.width\"\n [style.height.px]=\"hover.rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">{{ inspectLabel(hover.entries) }}</span>\n </div>\n}\n\n@if (locatedRect(); as rect) {\n <div\n [style.left.px]=\"rect.left\"\n [style.top.px]=\"rect.top\"\n [style.width.px]=\"rect.width\"\n [style.height.px]=\"rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">created here</span>\n </div>\n}\n", styles: ["@layer components{.et-query-devtools-host{--_et-qdt-bg: var(--et-surface-background-solid, rgb(24 24 27));--_et-qdt-fg: var(--et-surface-color-solid, rgb(244 244 245));--_et-qdt-muted: var(--et-surface-color-muted-solid, rgb(161 161 170));--_et-qdt-subtle: var(--et-surface-color-subtle-solid, rgb(113 113 122));--_et-qdt-border: var(--et-surface-border-solid, rgb(255 255 255 / .12));--_et-qdt-interaction: var(--et-surface-interaction-solid, rgb(255 255 255));--_et-qdt-line: color-mix(in srgb, var(--_et-qdt-interaction) 24%, transparent);--_et-qdt-line-strong: color-mix(in srgb, var(--_et-qdt-interaction) 42%, transparent);--_et-qdt-accent: var(--et-theme-color-primary-solid, rgb(96 165 250));--_et-qdt-hover: color-mix(in srgb, var(--_et-qdt-interaction) 10%, transparent);--_et-qdt-field: color-mix(in srgb, var(--_et-qdt-interaction) 8%, transparent);--_et-qdt-active: color-mix(in srgb, var(--_et-qdt-interaction) 18%, transparent);--_et-qdt-success: rgb(34 197 94);--_et-qdt-error: rgb(239 68 68);--_et-qdt-loading: rgb(234 179 8);--_et-qdt-title-bar: 2.4rem;--_et-qdt-radius-sm: .3rem;--_et-qdt-radius: .5rem;--_et-qdt-radius-lg: .6rem;--_et-qdt-control-h: 2.4rem;--_et-qdt-row-h: 3.6rem;position:fixed;inset-inline:0;inset-block-end:0;z-index:2147483010;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,Liberation Mono,monospace;font-size:1.2rem;line-height:1.4;pointer-events:none}.et-query-devtools-host>*{pointer-events:auto}@media(pointer:coarse){.et-query-devtools-host{--_et-qdt-control-h: 3.4rem;--_et-qdt-row-h: 4.4rem}}.et-query-devtools-panel{position:relative;display:flex;flex-direction:column;inline-size:100%;min-block-size:20rem;max-block-size:90vh;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border-block-start:1px solid var(--_et-qdt-border);box-shadow:0 -.4rem 2rem #00000059}.et-query-devtools-host:where([data-dock=right]){inset-block:0;inset-inline-start:auto}.et-query-devtools-panel:where([data-dock=right]){inline-size:auto;block-size:100%;max-block-size:none;max-inline-size:90vw;border-block-start:0;border-inline-start:1px solid var(--_et-qdt-border);box-shadow:-.4rem 0 2rem #00000059}.et-query-devtools-host:where([data-dock=float]){inset:0}.et-query-devtools-panel:where([data-dock=float]){--_et-qdt-frame: .8rem;--et-resize-handles-edge-size: var(--_et-qdt-frame);--et-resize-handles-touch-edge-size: var(--_et-qdt-frame);--et-resize-handles-corner-size: 1.6rem;--et-resize-handles-touch-corner-size: 1.6rem;--et-resize-handles-edge-inset: 1.6rem;--et-resize-handles-side-top: var(--_et-qdt-title-bar);--et-resize-handles-side-bottom: 1.6rem;--et-resize-handles-z-index: 3;position:absolute;inline-size:auto;min-block-size:0;max-block-size:none;padding-block-start:var(--_et-qdt-title-bar);padding-inline:var(--_et-qdt-frame);padding-block-end:var(--_et-qdt-frame);border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius-lg);box-shadow:0 1rem 4rem #00000073;overflow:hidden}.et-query-devtools-move{position:absolute;inset-inline:0;inset-block-start:0;block-size:var(--_et-qdt-title-bar);cursor:move;z-index:2}.et-query-devtools-panel:where([data-dock=float]) .et-query-devtools-body{border-radius:var(--_et-qdt-radius-sm);overflow:hidden}.et-query-devtools-move:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.9rem;inline-size:3rem;margin-inline-start:-1.5rem;block-size:.6rem;background-image:radial-gradient(currentcolor 40%,transparent 42%);background-size:.4rem .3rem;color:var(--_et-qdt-subtle)}@media(hover:hover){.et-query-devtools-move:hover:after{color:var(--_et-qdt-accent)}}.et-query-devtools-panel:where([data-dock=float][data-parked]){opacity:.85}@media(hover:hover){.et-query-devtools-panel:where([data-dock=float][data-parked]):hover{opacity:1}}.et-query-devtools-panel:where([data-dock=float][data-parked]) .et-query-devtools-move{cursor:grab;background:var(--_et-qdt-active)}.et-query-devtools-panel:where([data-dock=float][data-parked]) .et-query-devtools-move:after{color:var(--_et-qdt-accent)}.et-query-devtools-host:where([data-dock=left]){inset-block:0;inset-inline-end:auto}.et-query-devtools-panel:where([data-dock=left]){inline-size:auto;block-size:100%;max-block-size:none;max-inline-size:90vw;border-block-start:0;border-inline-end:1px solid var(--_et-qdt-border);box-shadow:.4rem 0 2rem #00000059}.et-query-devtools-host:where([data-dock=top]){inset-block-start:0;inset-block-end:auto}.et-query-devtools-panel:where([data-dock=top]){border-block-start:0;border-block-end:1px solid var(--_et-qdt-border);box-shadow:0 .4rem 2rem #00000059}.et-query-devtools-host:where([data-dock=popout]){position:static;inline-size:100%;block-size:100%}.et-query-devtools-panel:where([data-dock=popout]){block-size:100%;max-block-size:none;border:0;box-shadow:none}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-header{flex-wrap:wrap}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-tabs,.et-query-devtools-header-actions){flex:1 1 auto;flex-wrap:nowrap;min-inline-size:0;overflow-x:auto}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-tabs,.et-query-devtools-header-actions)>*{flex:none}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-master-detail,.et-query-devtools-split){flex-direction:column}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-list,.et-query-devtools-detail,.et-query-devtools-drawer,.et-query-devtools-split .et-query-devtools-scroll){flex:1 1 50%;min-inline-size:0;min-block-size:0;max-inline-size:none;border-inline-start:0;border-inline-end:0}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-detail,.et-query-devtools-drawer){border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-master-detail>.et-query-devtools-list{flex:0 1 var(--_et-qdt-list-height, 50%);min-block-size:var(--_et-qdt-list-height, 0);max-block-size:var(--_et-qdt-list-height, none)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-split .et-query-devtools-drawer{flex:0 1 var(--_et-qdt-drawer-height, 50%);min-block-size:var(--_et-qdt-drawer-height, 0);max-block-size:var(--_et-qdt-drawer-height, none)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-pane-resize{cursor:ns-resize}.et-query-devtools-panel--resizing{-webkit-user-select:none;user-select:none}.et-query-devtools-resize,.et-query-devtools-pane-resize{touch-action:none}.et-query-devtools-resize{position:absolute;inset-inline:0;inset-block-start:0;block-size:.6rem;cursor:ns-resize;z-index:1}.et-query-devtools-panel:where([data-dock=right]) .et-query-devtools-resize{inset-inline:0 auto;inset-block:0;inline-size:.6rem;block-size:auto;cursor:ew-resize}.et-query-devtools-panel:where([data-dock=left]) .et-query-devtools-resize{inset-inline:auto 0;inset-block:0;inline-size:.6rem;block-size:auto;cursor:ew-resize}.et-query-devtools-panel:where([data-dock=top]) .et-query-devtools-resize{inset-block:auto 0}.et-query-devtools-resize:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.2rem;inline-size:4rem;margin-inline-start:-2rem;block-size:.2rem;border-radius:999px;background:var(--_et-qdt-border)}.et-query-devtools-panel:where([data-dock=right]) .et-query-devtools-resize:after{inset-inline:.2rem auto;inset-block:50% auto;inline-size:.2rem;block-size:4rem;margin-inline-start:0;margin-block-start:-2rem}.et-query-devtools-panel:where([data-dock=left]) .et-query-devtools-resize:after{inset-inline:auto .2rem;inset-block:50% auto;inline-size:.2rem;block-size:4rem;margin-inline-start:0;margin-block-start:-2rem}.et-query-devtools-panel:where([data-dock=top]) .et-query-devtools-resize:after{inset-block:auto .2rem}@media(hover:hover){.et-query-devtools-resize:hover:after{background:var(--_et-qdt-accent)}}.et-query-devtools-header{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding-inline:.8rem;border-block-end:1px solid var(--_et-qdt-border);flex:0 0 auto}.et-query-devtools-armed-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-block-end:1px solid color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-armed-text{flex:1 1 auto;min-inline-size:0}.et-query-devtools-notice-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:var(--_et-qdt-hover);border-block-end:1px solid var(--_et-qdt-border);color:var(--_et-qdt-fg);font-size:1.1rem}.et-query-devtools-notice-bar .et-query-devtools-btn{flex:0 0 auto}.et-query-devtools-armed-orphans{display:block;opacity:.85}.et-query-devtools-armed-dot{flex:0 0 auto;inline-size:.8rem;block-size:.8rem;border-radius:50%;background:var(--_et-qdt-error);animation:et-query-devtools-armed-pulse 1.6s ease-in-out infinite}@media(prefers-reduced-motion:reduce){.et-query-devtools-armed-dot{animation:none}}@keyframes et-query-devtools-armed-pulse{50%{opacity:.25}}.et-query-devtools-armed-bar .et-query-devtools-btn{flex:0 0 auto;border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-tabs{display:flex;flex-wrap:wrap;gap:.2rem}.et-query-devtools-tab{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:none;background:transparent;color:var(--_et-qdt-muted);padding:.9rem 1rem;cursor:pointer;border-block-end:2px solid transparent;font:inherit}@media(hover:hover){.et-query-devtools-tab:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-tab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-tab-badge{display:inline-block;padding-inline:.45rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-interaction) 12%,transparent);color:var(--_et-qdt-muted);font-size:1rem;font-variant-numeric:tabular-nums}.et-query-devtools-tab-badge--error{background:color-mix(in srgb,var(--_et-qdt-error) 25%,transparent);color:var(--_et-qdt-fg)}.et-query-devtools-menu{display:contents}.et-query-devtools-menu-caret{font-size:.9em;opacity:.7}.et-query-devtools-menu-glyph{display:inline-block;min-inline-size:1.2rem;text-align:center}.et-query-devtools-menu>[data-menu-open]{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}.et-menu .et-query-devtools-menu-item-glyph{display:inline-block;inline-size:16px;text-align:center}.et-menu-item-content:has(.et-query-devtools-menu-item-note){display:flex;flex-direction:column;align-items:start;gap:2px;padding-block:8px}.et-query-devtools-menu-item-note{color:var(--et-surface-color-muted-solid, rgb(255 255 255 / .55));font-size:12px}.et-query-devtools-header-actions{display:flex;align-items:center;gap:.2rem;flex:0 0 auto}.et-query-devtools-header-divider{inline-size:1px;block-size:1.6rem;margin-inline:.4rem;background:var(--_et-qdt-border)}.et-query-devtools-inspect-btn{position:relative}.et-query-devtools-inspect-icon{inline-size:1.4rem;block-size:1.4rem;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.25;stroke-linecap:round}.et-query-devtools-inspect-icon-dot{fill:currentColor;stroke:none}.et-query-devtools-btn.et-query-devtools-inspect-btn--active,.et-query-devtools-btn.et-query-devtools-inspect-btn--active:hover{color:#09090b;background:var(--_et-qdt-accent);border-color:var(--_et-qdt-accent)}.et-query-devtools-inspect-btn--active:after{content:\"\";position:absolute;inset:-.3rem;border:1px solid var(--_et-qdt-accent);border-radius:calc(var(--_et-qdt-radius) + .3rem);pointer-events:none;animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}@keyframes et-query-devtools-inspect-pulse{0%,to{opacity:.2}50%{opacity:1}}@media(prefers-reduced-motion:reduce){.et-query-devtools-inspect-btn--active:after,.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:none;opacity:1}}.et-query-devtools-kbd{border-radius:var(--_et-qdt-radius-sm);background:var(--_et-qdt-hover);box-shadow:inset 0 0 0 1px var(--_et-qdt-border);color:var(--_et-qdt-muted);padding:.15rem .4rem;font:inherit;font-size:1.1rem;line-height:1.2;white-space:nowrap}.et-query-devtools-kbd--inverse{background:#00000026;box-shadow:inset 0 0 0 1px #00000040;color:inherit}@media(hover:none)and (pointer:coarse){.et-query-devtools-kbd{display:none}}.et-query-devtools-inspect-box{position:fixed;z-index:2147483002;pointer-events:none;border:2px solid var(--_et-qdt-accent);background:color-mix(in srgb,var(--_et-qdt-accent) 15%,transparent);border-radius:var(--_et-qdt-radius-sm)}.et-query-devtools-inspect-tag{position:absolute;inset-block-start:100%;inset-inline-start:0;margin-block-start:.2rem;padding:.2rem .5rem;border-radius:var(--_et-qdt-radius-sm);background:var(--_et-qdt-accent);color:#09090b;font-family:ui-monospace,monospace;font-size:1.1rem;font-weight:600;white-space:nowrap}.et-query-devtools-panel :is(a,button,input,select,summary,textarea,[tabindex]):focus-visible{outline:2px solid var(--_et-qdt-accent);outline-offset:1px}.et-query-devtools-body{flex:1 1 auto;min-block-size:0;overflow:hidden}.et-query-devtools-detail{flex:1 1 auto;min-inline-size:0;overflow-y:auto;padding:1rem}.et-query-devtools-detail .et-query-devtools-meta,.et-query-devtools-drawer .et-query-devtools-meta{padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-detail .et-query-devtools-section-row,.et-query-devtools-drawer .et-query-devtools-section-row{padding-block-start:.8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-filter-chip{display:inline-flex;align-items:center;gap:.6rem;padding:.3rem .35rem .3rem .7rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_et-qdt-accent) 45%,transparent);color:var(--_et-qdt-accent);font-size:1.1rem;font-weight:600;letter-spacing:.02em}.et-query-devtools-filter-icon{font-size:1.3rem;line-height:1}.et-query-devtools-filter-count{border-radius:999px;background:var(--_et-qdt-accent);color:#09090b;min-inline-size:1.8rem;padding:0 .5rem;text-align:center}.et-query-devtools-filter-clear{appearance:none;display:inline-flex;align-items:center;gap:.5rem;min-block-size:var(--_et-qdt-control-h);border:1px solid var(--_et-qdt-line);border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-fg);cursor:pointer;padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-filter-clear:hover{border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent);color:var(--_et-qdt-fg)}}.et-query-devtools-scroll{block-size:100%;overflow:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.et-query-devtools-split{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-split .et-query-devtools-scroll{flex:1 1 auto;min-inline-size:0}.et-query-devtools-pane{display:flex;flex-direction:column;flex:1 1 auto;block-size:100%;min-inline-size:0;min-block-size:0;overflow:hidden}.et-query-devtools-pane>.et-query-devtools-scroll{padding-block:0 1rem}.et-query-devtools-pane-resize{flex:0 0 .5rem;align-self:stretch;cursor:ew-resize;background:transparent;transition:background .12s ease}.et-query-devtools-pane-resize:active{background:var(--_et-qdt-accent)}@media(hover:hover){.et-query-devtools-pane-resize:hover{background:var(--_et-qdt-accent)}}.et-query-devtools-toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem;padding:.8rem;position:sticky;inset-block-start:0;z-index:1;background:var(--_et-qdt-bg);border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-scroll>.et-query-devtools-toolbar{inset-block-start:-1rem}.et-query-devtools-toolbar-row{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 100%;gap:.5rem}.et-query-devtools-field{display:inline-flex;align-items:center;gap:.6rem;color:var(--_et-qdt-muted)}.et-query-devtools-select{flex:0 0 auto;min-block-size:var(--_et-qdt-control-h);background:color-mix(in srgb,var(--_et-qdt-interaction) 8%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .8rem;font:inherit;font-size:1.15rem}.et-query-devtools-select:focus-visible{border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent)}.et-query-devtools-select option{background-color:var(--_et-qdt-bg);color:var(--_et-qdt-fg)}.et-query-devtools-count{color:var(--_et-qdt-subtle)}.et-query-devtools-row{display:flex;align-items:stretch;inline-size:100%;min-block-size:var(--_et-qdt-row-h);border-block-end:1px solid var(--_et-qdt-border);color:var(--_et-qdt-fg)}@media(hover:hover){.et-query-devtools-row:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-method{font-weight:700;font-size:1.1rem;color:var(--_et-qdt-muted);white-space:nowrap}.et-query-devtools-route{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1 1 auto;min-inline-size:0}.et-query-devtools-status{inline-size:.8rem;block-size:.8rem;border-radius:999px;flex:0 0 auto;background:var(--_et-qdt-subtle)}.et-query-devtools-status[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-status[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-status[data-status=loading],.et-query-devtools-status[data-status=running]{background:var(--_et-qdt-loading)}.et-query-devtools-btn{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:.5rem;flex:0 0 auto;min-block-size:var(--_et-qdt-control-h);border:1px solid var(--_et-qdt-line);border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-fg);padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4;white-space:nowrap;cursor:pointer;transition:color .12s ease,background-color .12s ease,border-color .12s ease}@media(hover:hover){.et-query-devtools-btn:hover:not(:disabled){background:var(--_et-qdt-hover);border-color:var(--_et-qdt-line-strong);color:var(--_et-qdt-fg)}}.et-query-devtools-btn:disabled{border-color:var(--_et-qdt-border);color:var(--_et-qdt-subtle);cursor:default;background:transparent;opacity:.6}.et-query-devtools-btn--primary,.et-query-devtools-btn--primary:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 40%,transparent);color:var(--_et-qdt-accent)}@media(hover:hover){.et-query-devtools-btn--primary:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-accent) 26%,transparent)}}.et-query-devtools-btn--danger{border-color:color-mix(in srgb,var(--_et-qdt-error) 32%,transparent);color:color-mix(in srgb,var(--_et-qdt-error) 55%,var(--_et-qdt-fg))}@media(hover:hover){.et-query-devtools-btn--danger:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-error) 16%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);color:var(--_et-qdt-error)}}.et-query-devtools-btn--tampered,.et-query-devtools-btn--tampered:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-icon-btn{inline-size:var(--_et-qdt-control-h);padding-inline:0;font-size:1.3rem}.et-query-devtools-btn-group{display:inline-flex;flex:0 0 auto;border:1px solid var(--_et-qdt-line);border-radius:var(--_et-qdt-radius);overflow:hidden}.et-query-devtools-btn-group>.et-query-devtools-btn{border-radius:0;border-block:0;border-inline-start:0;border-inline-end:1px solid var(--_et-qdt-line)}.et-query-devtools-btn-group>.et-query-devtools-btn:last-child{border-inline-end:0}.et-query-devtools-btn-group>.et-query-devtools-btn:focus-visible{outline-offset:-2px}.et-query-devtools-toolbar-btn{margin-inline-start:auto;flex:0 0 auto}.et-query-devtools-meta{display:grid;grid-template-columns:auto 1fr;gap:.3rem 1rem;margin:0 0 1rem}.et-query-devtools-meta dt{color:var(--_et-qdt-muted)}.et-query-devtools-meta dd{margin:0;overflow-wrap:anywhere}.et-query-devtools-section{margin:1rem 0 .4rem;font-size:1.1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat{padding:.6rem .8rem;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-stat dt{font-size:1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat dd{margin:0;font-size:1.5rem;font-variant-numeric:tabular-nums}.et-query-devtools-stat-sub{display:block;font-size:1rem;color:var(--_et-qdt-subtle)}.et-query-devtools-json{margin:0;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow-x:auto;white-space:pre-wrap;overflow-wrap:anywhere;max-block-size:24rem;overflow-y:auto}.et-query-devtools-json--error{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-mono{font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.et-query-devtools-url{display:inline-block;max-inline-size:32rem;margin-inline-start:.6rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.et-query-devtools-chips,.et-query-devtools-chip-row{display:flex;flex-wrap:wrap;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-chip{display:inline-flex;align-items:center;gap:.3rem;padding:.2rem .6rem;border-radius:999px;border:1px solid var(--_et-qdt-border);font-size:1.1rem;color:var(--_et-qdt-fg);background:color-mix(in srgb,var(--_et-qdt-interaction) 6%,transparent)}.et-query-devtools-chip--muted{color:var(--_et-qdt-muted)}.et-query-devtools-retry-glyph{font-size:.7em;line-height:1}.et-query-devtools-chip--loading{color:var(--_et-qdt-loading);border-color:color-mix(in srgb,var(--_et-qdt-loading) 50%,transparent)}.et-query-devtools-chip--error{color:var(--_et-qdt-error);border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-chip--success{color:var(--_et-qdt-success);border-color:color-mix(in srgb,var(--_et-qdt-success) 50%,transparent)}button.et-query-devtools-chip--button{appearance:none;border-color:var(--_et-qdt-line);cursor:pointer;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}@media(hover:hover){button.et-query-devtools-chip--button:hover{border-color:var(--_et-qdt-accent);color:var(--_et-qdt-accent)}}.et-query-devtools-facet{appearance:none;border-color:var(--_et-qdt-line);min-block-size:var(--_et-qdt-control-h);padding-inline:.8rem;background:transparent;cursor:pointer;color:var(--_et-qdt-muted);font:inherit;font-size:1.1rem;line-height:1.4}@media(hover:hover){.et-query-devtools-facet:hover:not(:disabled){color:var(--_et-qdt-fg);background:var(--_et-qdt-hover);border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent)}}.et-query-devtools-facet:disabled{color:var(--_et-qdt-subtle);cursor:default}.et-query-devtools-facet--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 14%,transparent)}.et-query-devtools-facet-count{font-variant-numeric:tabular-nums;color:inherit;opacity:.75}.et-query-devtools-card{border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius-lg);padding:1rem}.et-query-devtools-card--armed{border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-card-head{display:flex;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-inner-queries{display:flex;flex-direction:column;gap:.3rem}.et-query-devtools-inner-row{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-fg);padding:.4rem .6rem;cursor:pointer;font:inherit}@media(hover:hover){.et-query-devtools-inner-row:hover:not([disabled]){background:var(--_et-qdt-hover)}}.et-query-devtools-inner-row--active{background:var(--_et-qdt-active);border-color:var(--_et-qdt-accent)}.et-query-devtools-inner-row[disabled]{cursor:default;opacity:.6}.et-query-devtools-step-toggle{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:none;background:transparent;color:var(--_et-qdt-fg);padding:.6rem;cursor:pointer;font:inherit}@media(hover:hover){.et-query-devtools-step-toggle:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-step-index{flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret{color:var(--_et-qdt-muted)}.et-query-devtools-table{inline-size:100%;border-collapse:collapse}.et-query-devtools-table-scroll{overflow-x:auto}.et-query-devtools-table-scroll>.et-query-devtools-table{min-inline-size:max-content}.et-query-devtools-table th,.et-query-devtools-table td{text-align:start;padding:.5rem .6rem;border-block-end:1px solid var(--_et-qdt-border);vertical-align:top}.et-query-devtools-table td .et-query-devtools-chip{padding-block:0;line-height:1.4}.et-query-devtools-table th{color:var(--_et-qdt-muted);font-weight:600;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-table tr[data-status=error] td{color:var(--_et-qdt-error)}.et-query-devtools-table .et-query-devtools-cell-origin{inline-size:12ch;max-inline-size:12ch;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;overflow-wrap:normal}.et-query-devtools-empty{color:var(--_et-qdt-subtle);padding:1.2rem;text-align:center}.et-query-devtools-cell-actions{display:inline-flex;gap:.4rem;white-space:nowrap}.et-query-devtools-section-row{display:flex;align-items:center;justify-content:space-between;gap:.8rem;margin-block-start:1rem}.et-query-devtools-search{flex:1 1 16rem;min-inline-size:0;min-block-size:var(--_et-qdt-control-h);background:var(--_et-qdt-field);color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .8rem;font:inherit;font-size:1.15rem}.et-query-devtools-search:focus{outline:none;border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:var(--_et-qdt-bg)}.et-query-devtools-row-link{display:inline-flex;align-items:center;gap:.6rem;appearance:none;border:0;background:none;padding:0;color:inherit;font:inherit;text-align:start;cursor:pointer}.et-query-devtools-row-link:focus-visible .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}@media(hover:hover){.et-query-devtools-row-link:hover .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}}.et-query-devtools-hint{flex:1 1 24rem;color:var(--_et-qdt-muted);font-size:1.1rem;margin:0}.et-query-devtools-input{inline-size:100%;min-block-size:var(--_et-qdt-control-h);background:var(--_et-qdt-field);color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .8rem;font:inherit;font-size:1.15rem}.et-query-devtools-input:focus{outline:none;border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:var(--_et-qdt-bg)}.et-query-devtools-input--number{inline-size:8rem;font-variant-numeric:tabular-nums}.et-query-devtools-editor{border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);padding:.8rem;margin-block-end:1rem}.et-query-devtools-editor-error{color:var(--_et-qdt-error);margin:.4rem 0 0}.et-query-devtools-btn--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsAuthTabComponent, selector: "et-query-devtools-auth-tab" }, { kind: "component", type: QueryDevtoolsBatchesTabComponent, selector: "et-query-devtools-batches-tab" }, { kind: "component", type: QueryDevtoolsCacheTabComponent, selector: "et-query-devtools-cache-tab" }, { kind: "component", type: QueryDevtoolsEventsTabComponent, selector: "et-query-devtools-events-tab" }, { kind: "component", type: QueryDevtoolsAboutComponent, selector: "et-query-devtools-about" }, { kind: "component", type: QueryDevtoolsSettingsComponent, selector: "et-query-devtools-settings" }, { kind: "component", type: QueryDevtoolsFaultsTabComponent, selector: "et-query-devtools-faults-tab" }, { kind: "component", type: QueryDevtoolsFormsTabComponent, selector: "et-query-devtools-forms-tab" }, { kind: "component", type: QueryDevtoolsLocksTabComponent, selector: "et-query-devtools-locks-tab" }, { kind: "component", type: QueryDevtoolsMocksTabComponent, selector: "et-query-devtools-mocks-tab" }, { kind: "component", type: QueryDevtoolsQueriesTabComponent, selector: "et-query-devtools-queries-tab" }, { kind: "component", type: QueryDevtoolsSequencesTabComponent, selector: "et-query-devtools-sequences-tab" }, { kind: "component", type: QueryDevtoolsSocketsTabComponent, selector: "et-query-devtools-sockets-tab" }, { kind: "component", type: QueryDevtoolsStacksTabComponent, selector: "et-query-devtools-stacks-tab" }, { kind: "component", type: QueryDevtoolsTimelineTabComponent, selector: "et-query-devtools-timeline-tab" }, { kind: "component", type: QueryDevtoolsToggleComponent, selector: "et-query-devtools-toggle", inputs: ["tampered"], outputs: ["openChange"] }, { kind: "directive", type: DragHandleDirective, selector: "[etDragHandle]", inputs: ["commitThreshold", "disabled"], outputs: ["dragTapped", "dragStarted", "dragMoved", "dragEnded", "dragCancelled"] }, { kind: "component", type: MenuCheckboxItemComponent, selector: "et-menu-checkbox-item", inputs: ["icon"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "component", type: MenuRadioGroupComponent, selector: "et-menu-radio-group" }, { kind: "component", type: MenuRadioItemComponent, selector: "et-menu-radio-item", inputs: ["icon"] }, { kind: "component", type: MenuSeparatorComponent, selector: "et-menu-separator" }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "component", type: ResizeHandlesComponent, selector: "et-resize-handles", inputs: ["edges", "disabled"], outputs: ["resizeStarted", "resizeMoved", "resizeEnded", "resizeCancelled"] }], encapsulation: i0.ViewEncapsulation.None }); }
7475
7853
  }
7476
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsComponent, decorators: [{
7854
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: QueryDevtoolsComponent, decorators: [{
7477
7855
  type: Component,
7478
7856
  args: [{ selector: 'et-query-devtools', encapsulation: ViewEncapsulation.None, imports: [
7479
7857
  QueryDevtoolsAuthTabComponent,