From 77560296cd7e4526428415918676af1f7984a785 Mon Sep 17 00:00:00 2001 From: erKam Date: Sat, 7 Nov 2026 17:33:38 +0000 Subject: [PATCH 01/32] ci: add oxlint with a ratchet on new warnings pnpm lint runs oxlint (correctness rules plus no-explicit-any) and fails when a file has more warnings of a rule than lint-baseline.json allows. The baseline only goes down. CI runs it after the formatting check. Refs #373 --- .claude/skills/devtools-verify/SKILL.md | 1 + .github/workflows/ci.yml | 3 + .oxlintrc.json | 18 + AGENTS.md | 23 +- .../src/content/contributing/development.md | 7 +- docs/contributing/coding-standards.md | 2 +- lint-baseline.json | 414 ++++++++++++++++++ package.json | 2 + packages/devtools/CHANGELOG.md | 1 + .../src/__tests__/lint-ratchet.test.ts | 44 ++ pnpm-lock.yaml | 217 +++++++++ scripts/lint-ratchet.d.mts | 9 + scripts/lint-ratchet.mjs | 148 +++++++ 13 files changed, 875 insertions(+), 14 deletions(-) create mode 100644 .oxlintrc.json create mode 100644 lint-baseline.json create mode 100644 packages/devtools/src/__tests__/lint-ratchet.test.ts create mode 100644 scripts/lint-ratchet.d.mts create mode 100644 scripts/lint-ratchet.mjs diff --git a/.claude/skills/devtools-verify/SKILL.md b/.claude/skills/devtools-verify/SKILL.md index 82bbeddd..108be298 100644 --- a/.claude/skills/devtools-verify/SKILL.md +++ b/.claude/skills/devtools-verify/SKILL.md @@ -11,6 +11,7 @@ Run them in this order; all must pass: ```sh pnpm format:check +pnpm lint pnpm commit:check pnpm skills:check pnpm typecheck diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 905e3088..3f87d0cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,9 @@ jobs: - name: Check formatting run: pnpm format:check + - name: Lint (no new oxlint warnings) + run: pnpm lint + - name: Validate agent skills and roles run: pnpm skills:check diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 00000000..aa793af9 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,18 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript", "unicorn", "oxc"], + "categories": { + "correctness": "warn" + }, + "rules": { + "typescript/no-explicit-any": "warn" + }, + "ignorePatterns": [ + "**/dist/**", + "**/.angular/**", + "**/node_modules/**", + "extension/ui/**", + "examples/nativescript/platforms/**", + "examples/nativescript/hooks/**" + ] +} diff --git a/AGENTS.md b/AGENTS.md index 3347808b..7fd60e90 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,17 +14,18 @@ Pangular Inspector inspects a running Angular app and serves what it finds to a ### Commands -| Command | What it runs | -| ---------------------- | ------------------------------------------------------------------------- | -| `pnpm test:devtools` | Package tests | -| `pnpm test:panel` | Panel tests | -| `pnpm test:axe` | Builds a static report and runs axe on every panel view | -| `pnpm typecheck` | TypeScript and template checks for every project | -| `pnpm format:check` | Prettier, including the docs markdown | -| `pnpm skills:check` | Checks the skills and roles in `.claude` and the skills the package ships | -| `pnpm docs:build` | Builds the docs site with its link guards | -| `pnpm extension:build` | Rebuilds `extension/ui` from `app` | -| `pnpm commit:check` | Checks the commit messages on the branch | +| Command | What it runs | +| ---------------------- | --------------------------------------------------------------------------- | +| `pnpm test:devtools` | Package tests | +| `pnpm test:panel` | Panel tests | +| `pnpm test:axe` | Builds a static report and runs axe on every panel view | +| `pnpm typecheck` | TypeScript and template checks for every project | +| `pnpm format:check` | Prettier, including the docs markdown | +| `pnpm lint` | oxlint with a ratchet: no file gets more warnings than `lint-baseline.json` | +| `pnpm skills:check` | Checks the skills and roles in `.claude` and the skills the package ships | +| `pnpm docs:build` | Builds the docs site with its link guards | +| `pnpm extension:build` | Rebuilds `extension/ui` from `app` | +| `pnpm commit:check` | Checks the commit messages on the branch | The words this project uses for its own concepts (overlay, collector, hub, devframe, agent tool and so on) are defined in `docs/CONTEXT.md`. Use them the same way in code, docs and issues. diff --git a/apps/docs/src/content/contributing/development.md b/apps/docs/src/content/contributing/development.md index 7cf643a0..ec4251cc 100644 --- a/apps/docs/src/content/contributing/development.md +++ b/apps/docs/src/content/contributing/development.md @@ -146,6 +146,7 @@ Run the same checks as CI before you open a PR: ```bash pnpm format:check # Prettier +pnpm lint # oxlint, with no new warnings over lint-baseline.json pnpm typecheck # Host app + specs, devtools UI and Analog demo (with templates), devtools package + its tests pnpm exec nx affected -t test build # Test and build affected projects pnpm test:devtools # Devtools package tests (Vitest) @@ -168,6 +169,8 @@ The mobile overlays (NativeScript, Angular Native and the Capacitor collector) h `pnpm test:coverage` runs `pnpm test:devtools --coverage`, then `pnpm test:panel --coverage`, each with `--testTimeout=20000`: v8 instrumentation slows every test, and the slowest ones went over the default 5 seconds. The v8 provider writes an HTML report, `coverage-summary.json` and `lcov.info` to `dist/coverage/devtools` and `dist/coverage/panel`, also when a test fails. There is no threshold: the report shows what the tests reach and nothing fails on a low number. Open `dist/coverage/devtools/index.html` to read it. +`pnpm lint` runs oxlint with `.oxlintrc.json` (the correctness rules plus `typescript/no-explicit-any`) and compares the warnings of each file and rule with `lint-baseline.json`. A file with more warnings of a rule than the baseline fails the check, also a new file. The baseline only goes down: after you fix warnings, `pnpm lint --update` lowers it, and it refuses to raise a count unless you pass `--allow-new`. + `pnpm skills:check` validates the frontmatter of every skill and role and checks that the files and links they mention exist. `pnpm commit:check` checks every commit on your branch that is not on `main` (it compares with `upstream/main`, then `origin/main`, then `main`). ### What CI runs @@ -178,8 +181,8 @@ The mobile overlays (NativeScript, Angular Native and the Capacitor collector) h pnpm install --frozen-lockfile on the Node version from .nvmrc. - - pnpm format:check, pnpm skills:check, then pnpm typecheck. + + pnpm format:check, pnpm lint, pnpm skills:check, then pnpm typecheck. nx affected -t test build, compared against the last green commit on main. diff --git a/docs/contributing/coding-standards.md b/docs/contributing/coding-standards.md index 0eb84012..d48a57af 100644 --- a/docs/contributing/coding-standards.md +++ b/docs/contributing/coding-standards.md @@ -4,7 +4,7 @@ These rules go together with the Angular rules in [`AGENTS.md`](../../AGENTS.md) ## TypeScript -- Strict mode everywhere. Avoid `any`; use `unknown` and narrow it, or a small local interface. +- Strict mode everywhere. Avoid `any`; use `unknown` and narrow it, or a small local interface. `pnpm lint` (oxlint) warns on `any` and fails when a file gets more warnings than `lint-baseline.json` allows. - Prefer `const`. Use `readonly` for members that never change. - Names describe what a thing is or does. Spell words out. Booleans start with `is`, `has` or `should`. - No `I` prefix on interfaces and no `$` suffix on observables. diff --git a/lint-baseline.json b/lint-baseline.json new file mode 100644 index 00000000..31d875da --- /dev/null +++ b/lint-baseline.json @@ -0,0 +1,414 @@ +{ + "app/src/__tests__/component-tree-hydration.test.ts": { + "eslint(no-unused-vars)": 1 + }, + "app/src/__tests__/hub-rail-style.test.ts": { + "eslint(no-unused-vars)": 1 + }, + "app/src/__tests__/timeline.test.ts": { + "unicorn(no-useless-spread)": 1 + }, + "app/src/__tests__/virtual-window.test.ts": { + "eslint(no-setter-return)": 1 + }, + "app/src/pages/store-types.ts": { + "typescript(no-explicit-any)": 1 + }, + "apps/docs/image-sizes.spec.ts": { + "unicorn(no-new-array)": 1 + }, + "apps/docs/search-index.plugin.ts": { + "eslint(no-control-regex)": 1 + }, + "apps/docs/src/app/utils/heading-slug.ts": { + "eslint(no-control-regex)": 1 + }, + "examples/nativescript/references.d.ts": { + "typescript(triple-slash-reference)": 1 + }, + "packages/devtools/src/__tests__/analog-load-inherit.test.ts": { + "typescript(no-explicit-any)": 2 + }, + "packages/devtools/src/__tests__/analog-page-calls.test.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/__tests__/async-source.test.ts": { + "unicorn(no-thenable)": 1 + }, + "packages/devtools/src/__tests__/audit-findings.test.ts": { + "eslint(no-unused-vars)": 1 + }, + "packages/devtools/src/__tests__/audit-tools.test.ts": { + "eslint(no-unsafe-optional-chaining)": 2, + "typescript(no-explicit-any)": 5 + }, + "packages/devtools/src/__tests__/axe-runner.test.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/__tests__/browser-tools.test.ts": { + "typescript(no-explicit-any)": 15 + }, + "packages/devtools/src/__tests__/cd-flash.test.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/__tests__/cd-live.test.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/__tests__/change-detection.test.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/__tests__/component-screenshot.test.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/__tests__/console-tap.test.ts": { + "typescript(no-this-alias)": 1 + }, + "packages/devtools/src/__tests__/extension-origin.test.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/__tests__/extension-panel-bridge.test.ts": { + "eslint(no-unused-vars)": 1 + }, + "packages/devtools/src/__tests__/forms-actions-fill-submit.test.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/__tests__/forms-actions.test.ts": { + "typescript(no-explicit-any)": 3 + }, + "packages/devtools/src/__tests__/forms-audit.test.ts": { + "typescript(no-explicit-any)": 12 + }, + "packages/devtools/src/__tests__/forms-collector.test.ts": { + "typescript(no-explicit-any)": 12 + }, + "packages/devtools/src/__tests__/forms-dotted-keys.test.ts": { + "typescript(no-explicit-any)": 10 + }, + "packages/devtools/src/__tests__/forms-mcp.test.ts": { + "typescript(no-explicit-any)": 3 + }, + "packages/devtools/src/__tests__/forms-read.test.ts": { + "typescript(no-explicit-any)": 22 + }, + "packages/devtools/src/__tests__/forms-real.test.ts": { + "typescript(no-explicit-any)": 6 + }, + "packages/devtools/src/__tests__/forms-scan.test.ts": { + "typescript(no-explicit-any)": 4 + }, + "packages/devtools/src/__tests__/forms-submit-reasons.test.ts": { + "typescript(no-explicit-any)": 3 + }, + "packages/devtools/src/__tests__/forms-submit-shared.test.ts": { + "typescript(no-explicit-any)": 2 + }, + "packages/devtools/src/__tests__/forms-webmcp.test.ts": { + "typescript(no-explicit-any)": 16 + }, + "packages/devtools/src/__tests__/forms.test.ts": { + "typescript(no-explicit-any)": 6 + }, + "packages/devtools/src/__tests__/http-rule-tools.test.ts": { + "eslint(no-unused-vars)": 1 + }, + "packages/devtools/src/__tests__/http.test.ts": { + "eslint(no-unused-vars)": 1, + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/__tests__/injector-component-link.test.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/__tests__/injector-tree.test.ts": { + "typescript(no-explicit-any)": 22 + }, + "packages/devtools/src/__tests__/leased-collectors.test.ts": { + "typescript(no-explicit-any)": 3 + }, + "packages/devtools/src/__tests__/lint-export.test.ts": { + "eslint(no-unused-vars)": 1 + }, + "packages/devtools/src/__tests__/native-diagnostics.test.ts": { + "unicorn(no-useless-spread)": 2 + }, + "packages/devtools/src/__tests__/ngrx-args.test.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/__tests__/ngrx-collector.test.ts": { + "typescript(no-explicit-any)": 61, + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/__tests__/ngrx-redaction-hot-path.test.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/__tests__/older-angular.test.ts": { + "unicorn(no-new-array)": 3 + }, + "packages/devtools/src/__tests__/overlay-angular-native.test.ts": { + "eslint(no-unsafe-optional-chaining)": 1 + }, + "packages/devtools/src/__tests__/overlay-dispose.test.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/__tests__/overlay-heartbeat.test.ts": { + "unicorn(no-useless-spread)": 1, + "unicorn(prefer-string-starts-ends-with)": 1 + }, + "packages/devtools/src/__tests__/overlay-link.test.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/__tests__/overlay-nativescript-connection.test.ts": { + "eslint(no-unsafe-optional-chaining)": 1 + }, + "packages/devtools/src/__tests__/overlay-reconnect-visibility.test.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/__tests__/overlay-reconnect.test.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/__tests__/overlay-redaction.test.ts": { + "unicorn(no-useless-spread)": 2 + }, + "packages/devtools/src/__tests__/pipes-collector.test.ts": { + "typescript(no-explicit-any)": 5 + }, + "packages/devtools/src/__tests__/pipes-runtime.test.ts": { + "eslint(no-unsafe-optional-chaining)": 1, + "typescript(no-explicit-any)": 4 + }, + "packages/devtools/src/__tests__/plugins-collector.test.ts": { + "eslint(no-unsafe-optional-chaining)": 1, + "eslint(no-unused-vars)": 1, + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/__tests__/redux-devtools-hook.test.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/__tests__/redux-stores.test.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/__tests__/startup-timing.test.ts": { + "typescript(no-explicit-any)": 1, + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/__tests__/zone-collectors.test.ts": { + "typescript(no-explicit-any)": 1, + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/analog-runtime.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/browser-tools.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/component-form-masks.ts": { + "typescript(no-explicit-any)": 1, + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/component-screenshot.ts": { + "unicorn(prefer-string-starts-ends-with)": 1 + }, + "packages/devtools/src/console-tap.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/devframe.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/dom-diff.ts": { + "unicorn(no-new-array)": 1 + }, + "packages/devtools/src/error-explain.ts": { + "unicorn(prefer-string-starts-ends-with)": 1 + }, + "packages/devtools/src/errors-collector.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/forms-actions.ts": { + "eslint(no-unused-vars)": 1, + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/forms-collector.ts": { + "typescript(no-explicit-any)": 3, + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/forms-instrument.ts": { + "typescript(no-explicit-any)": 1, + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/forms-read.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/forms-submit.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/forms-webmcp.ts": { + "eslint(no-unused-vars)": 1, + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/forms.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/hub-messages.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/leaks-collector.ts": { + "typescript(no-explicit-any)": 1, + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/native-diagnostics.ts": { + "unicorn(no-useless-spread)": 2 + }, + "packages/devtools/src/ngrx-collector.ts": { + "typescript(no-explicit-any)": 1, + "unicorn(no-useless-fallback-in-spread)": 1, + "unicorn(no-useless-spread)": 3 + }, + "packages/devtools/src/ngrx-session.ts": { + "unicorn(prefer-string-starts-ends-with)": 1 + }, + "packages/devtools/src/ngrx-shared.ts": { + "eslint(no-control-regex)": 1 + }, + "packages/devtools/src/overlay-link.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/overlay.ts": { + "typescript(no-explicit-any)": 2 + }, + "packages/devtools/src/page-lease.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/pipes-collector.ts": { + "typescript(no-explicit-any)": 2 + }, + "packages/devtools/src/pipes-runtime.ts": { + "typescript(no-explicit-any)": 2 + }, + "packages/devtools/src/plugin.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/plugins-collector.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/push-delta.ts": { + "typescript(no-explicit-any)": 4 + }, + "packages/devtools/src/query-collector.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/redux-devtools-hook.ts": { + "typescript(no-explicit-any)": 1, + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/router-setup.ts": { + "unicorn(prefer-string-starts-ends-with)": 1 + }, + "packages/devtools/src/router.ts": { + "typescript(no-explicit-any)": 1, + "unicorn(prefer-string-starts-ends-with)": 2 + }, + "packages/devtools/src/rpc/__tests__/browser-tools-data.test.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/rpc/__tests__/browser-tools.test.ts": { + "eslint(no-unused-vars)": 2, + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/rpc/analog-register.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/rpc/analog-scan.ts": { + "unicorn(prefer-string-starts-ends-with)": 1 + }, + "packages/devtools/src/rpc/audit-axe.ts": { + "unicorn(prefer-string-starts-ends-with)": 1 + }, + "packages/devtools/src/rpc/audit-register.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/rpc/browser-tools.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/rpc/errors-register.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/rpc/get-pipes.ts": { + "eslint(no-unused-vars)": 1 + }, + "packages/devtools/src/rpc/get-routes.ts": { + "unicorn(prefer-string-starts-ends-with)": 2 + }, + "packages/devtools/src/rpc/http-waterfall.ts": { + "eslint(no-unused-vars)": 1 + }, + "packages/devtools/src/rpc/injection-explain.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/rpc/leaks-register.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/rpc/library-tools.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/rpc/page-interest.ts": { + "unicorn(no-useless-spread)": 3 + }, + "packages/devtools/src/rpc/pipes-agent.ts": { + "eslint(no-unused-vars)": 1 + }, + "packages/devtools/src/rpc/plugins-register.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/rpc/push-acks.ts": { + "unicorn(no-useless-spread)": 1 + }, + "packages/devtools/src/rpc/query-register.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/rpc/redux-stores-register.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/rpc/report-deltas.ts": { + "unicorn(no-useless-spread)": 5 + }, + "packages/devtools/src/rpc/server-routes.ts": { + "unicorn(prefer-string-starts-ends-with)": 1 + }, + "packages/devtools/src/rpc/snapshot-tools.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/rpc/startup-register.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/rpc/tool-input.ts": { + "unicorn(no-useless-fallback-in-spread)": 1 + }, + "packages/devtools/src/signal-masks.ts": { + "typescript(no-explicit-any)": 1 + }, + "packages/devtools/src/startup-recorder.ts": { + "typescript(no-explicit-any)": 1 + }, + "scripts/commit-message.mjs": { + "unicorn(prefer-string-starts-ends-with)": 1 + }, + "scripts/docs-screenshots.mjs": { + "eslint(no-control-regex)": 1 + }, + "scripts/e2e-install.mjs": { + "eslint(no-control-regex)": 2, + "unicorn(prefer-string-starts-ends-with)": 1 + }, + "scripts/e2e-mcp.mjs": { + "unicorn(prefer-string-starts-ends-with)": 1 + }, + "scripts/e2e-panel.mjs": { + "eslint(no-control-regex)": 1 + }, + "scripts/e2e-resilience.mjs": { + "eslint(no-unused-vars)": 3, + "unicorn(prefer-string-starts-ends-with)": 1 + }, + "scripts/e2e-security.mjs": { + "unicorn(prefer-string-starts-ends-with)": 2 + } +} diff --git a/package.json b/package.json index abec0fe4..6ba4c100 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "typecheck": "tsc -p tsconfig.app.json --noEmit && tsc -p tsconfig.spec.json --noEmit && ngc -p app/tsconfig.json --noEmit && ngc -p examples/analog/tsconfig.app.json --noEmit && tsc -p packages/devtools", "format": "prettier --write .", "format:check": "prettier --check .", + "lint": "node scripts/lint-ratchet.mjs", "serve:ssr:pangular-inspector": "node dist/pangular-inspector/server/server.mjs", "devtools:dev": "vite --config app/vite.config.ts", "devtools:build": "vite build --config app/vite.config.ts", @@ -91,6 +92,7 @@ "@vitest/coverage-v8": "4.1.11", "jsdom": "^28.0.0", "nx": "23.2.1", + "oxlint": "1.90.0", "playwright": "^1.63.0", "prettier": "^3.8.1", "sass": "^1.105.0", diff --git a/packages/devtools/CHANGELOG.md b/packages/devtools/CHANGELOG.md index 034683a0..51cb481f 100644 --- a/packages/devtools/CHANGELOG.md +++ b/packages/devtools/CHANGELOG.md @@ -241,6 +241,7 @@ What remains: `/config` exports `DEFAULT_POPUP_HOTKEY`, `PANGULAR_ACTIONS`, `PAN ### Release and CI +- `pnpm lint` runs oxlint with a ratchet: CI fails when a file gets more warnings of a rule than `lint-baseline.json` allows (#373). - The `leaks-real` test expects the rows the collector really reports (#430). - Pull requests run the security and MCP end-to-end checks, and a weekly job runs the rest. - A CI job installs the packed package on Node.js 22.12.0, the `engines.node` floor, and loads its CLI and its `cli`, `hub`, `ssr` and `vite` entry points there (#373). diff --git a/packages/devtools/src/__tests__/lint-ratchet.test.ts b/packages/devtools/src/__tests__/lint-ratchet.test.ts new file mode 100644 index 00000000..3d9ca23a --- /dev/null +++ b/packages/devtools/src/__tests__/lint-ratchet.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from 'vitest'; +import { compareCounts, countDiagnostics } from '../../../../scripts/lint-ratchet.mjs'; + +describe('lint ratchet', () => { + it('counts diagnostics per file and rule with sorted keys', () => { + const counts = countDiagnostics( + [ + { filename: 'b.ts', code: 'eslint(no-unused-vars)' }, + { filename: 'a.ts', code: 'typescript(no-explicit-any)' }, + { filename: 'b.ts', code: 'eslint(no-unused-vars)' }, + { filename: 'b.ts', code: 'eslint(no-control-regex)' }, + ], + '/repo', + ); + expect(counts).toEqual({ + 'a.ts': { 'typescript(no-explicit-any)': 1 }, + 'b.ts': { 'eslint(no-control-regex)': 1, 'eslint(no-unused-vars)': 2 }, + }); + expect(Object.keys(counts['b.ts']!)).toEqual([ + 'eslint(no-control-regex)', + 'eslint(no-unused-vars)', + ]); + }); + + it('fails on a new warning, also in a new file or for a new rule', () => { + const baseline = { 'a.ts': { any: 2 } }; + expect(compareCounts(baseline, { 'a.ts': { any: 3 } }).added).toEqual(['a.ts: any 2 -> 3']); + expect(compareCounts(baseline, { 'a.ts': { any: 2, unused: 1 } }).added).toEqual([ + 'a.ts: unused 0 -> 1', + ]); + expect(compareCounts(baseline, { 'a.ts': { any: 2 }, 'b.ts': { any: 1 } }).added).toEqual([ + 'b.ts: any 0 -> 1', + ]); + }); + + it('passes at the baseline and lists counts that went down', () => { + const baseline = { 'a.ts': { any: 2 }, 'b.ts': { unused: 1 } }; + expect(compareCounts(baseline, baseline)).toEqual({ added: [], fixed: [] }); + expect(compareCounts(baseline, { 'a.ts': { any: 1 } })).toEqual({ + added: [], + fixed: ['a.ts: any 2 -> 1', 'b.ts: unused 1 -> 0'], + }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6cb0f4d..f36dabf3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,6 +121,9 @@ importers: nx: specifier: 23.2.1 version: 23.2.1 + oxlint: + specifier: 1.90.0 + version: 1.90.0 playwright: specifier: ^1.63.0 version: 1.63.0 @@ -3248,6 +3251,128 @@ packages: '@oxc-project/types@0.150.0': resolution: {integrity: sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==} + '@oxlint/binding-android-arm-eabi@1.90.0': + resolution: {integrity: sha512-sgfalb1HuxYjiAa4wESZ626z1FGusqNcXYHyB5oIOEprS2sbPg0UYEfo83uF1GNG70DIfZxHkXuE6KFrlSdn8Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.90.0': + resolution: {integrity: sha512-RcKhz1ETMq+fazE9TKS2+5NIRGwigm/nW8egqF0LI+A8Yw2iW5r0E/C3XAPRiwIm/vcPiutIRIa6IQCIKhZsTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.90.0': + resolution: {integrity: sha512-PSrxMvP6mx+p1karZcUxLdy9lpaP+JWQPti4al9FgTaPhxE+pT/BjhGExloVVSWgZgIfOlLOjVyyq8BXlodhLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.90.0': + resolution: {integrity: sha512-OLbU9qsrV2Q8QxT2OEzOPdO8zCCpyI8qB+hDI8yr6RsYELtkf0FAZhJdfjM/RnLHKzt44HWwAPnk92vBtKofvw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.90.0': + resolution: {integrity: sha512-fBA4pWyVDARmNucQqpyFuPC49xYf1r+sX7dxPL5fNPQrSMLum39Sn56tZ9JPleWC7FzLPwSQ7plYYfirYBHF8A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.90.0': + resolution: {integrity: sha512-zhxH78eP2nR6/pGdhBc7MIfjhhcgATeu8fkmkFJbuzfk6Cj0FzBxyEhL85yDqrCybjx+G2YDzZGsxIO9kvO4Iw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.90.0': + resolution: {integrity: sha512-MvYmpHyJqUJjEvkZbJHdwfdL45cGn8EVBkYmoXFiaYFW9suoD3dbJl5mBdpy0DVgdjlyddmldbRtk3cCgETadQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.90.0': + resolution: {integrity: sha512-L13Sy+Kg0nZhwTsToniqjUFPA2TuM6962qCZ5Wp5KeB517Tw7ApdLTTtlb+bF86fcZZa2EtvCVvAJxk4Dya/MQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.90.0': + resolution: {integrity: sha512-wFDdZWGpzIPFdJFjSLWnKB0TGzFriTKUIrl04iMqMyM9BBIJTuerfCHYa0WpDSjr5x3lcW/CHLyacSt886WpGg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.90.0': + resolution: {integrity: sha512-FRywfn+MoKn8xpBSzgNwNVliSKRI2QXkxi/iF7o6FEQs8Bh8z2RpVTaFAcO3pchXjvB2eGNFIoLLQABIQ+u7VA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.90.0': + resolution: {integrity: sha512-IS2PaUgpc/qeLADTyhUNmdx3x4ijs9pjJdPKBYHaT+mAE7k3FsjnHPTjhpsak1b++h6CIPhv6WX9Z+y7Pw0voQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.90.0': + resolution: {integrity: sha512-s7JFKRkGS8O7i2ZVODfuO++NC7sH6HDqpMn8lYxw91w0SXJOxnAVjQ8utvmDwLMMT0jaSkuOKuUYJDGDtu4sHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.90.0': + resolution: {integrity: sha512-b0GxrOQr5VONCkd7KLSqmP6b6nqZuTpuS8QPQHzltmqdXP5bY0Jl0CZ0QmlCTrS3ExaLYbioFYOARzNbYvcApQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.90.0': + resolution: {integrity: sha512-iaJeShJtgUp8rZbt9YUdTAGOpmX5KogCLba17Jp1tWdJ3F4M3D2g2TKUyI1FMTHxKH9NNHuQcuC2kZRg6IV7yQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.90.0': + resolution: {integrity: sha512-/aZoXnL5WBLCXuzfHqvEFXcm4iwYmr/cYByHDTXzLFzJdE5DC7bm6phpWqHv6BeqpVgY9IpBAxGDv2WEkl5JCg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.90.0': + resolution: {integrity: sha512-SMhk7ozwoxYDKfYqGZCQEfULOKDneF9uhaoIf2neoDLWvNtjY9yVeL7YHmE5L9WdKvMpWNwwZxhenxBu6AgfOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.90.0': + resolution: {integrity: sha512-KBvR3A9fO7fCi2vCP8ifWvlia7tGEQV9NaYh+IGwa/7Groy48Mt/UaYqMK9/zOtAWl20BUCf6RPqzJ32fRw33Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.90.0': + resolution: {integrity: sha512-+x42rDHQGbTCuT3EzpWAzhP3NoVPE/cJsG4gnrakOSg0olV+GCdMpPU23Nvrl3zZMnXxFZdTtDv4D3SuaA5ZeQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.90.0': + resolution: {integrity: sha512-30JiaFEBUB9peEOcvVM8ggo88SoN/IdSr9DW505JdBji/R6dM66gKEiG65hw6qcaF2f4mO0cEJ+RwRZprmun9g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@parcel/watcher-android-arm64@2.6.0': resolution: {integrity: sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==} engines: {node: '>= 10.0.0'} @@ -6401,6 +6526,19 @@ packages: resolution: {integrity: sha512-kKR+jPiRJYJDexVoziIg/FVGvr1fT1FZSSJOk6tVoMKKSlsf1Cso+cgGCJkOEDWOP174vRntCPFKg+AS7InWvw==} engines: {node: ^20.19.0 || >=22.12.0} + oxlint@1.90.0: + resolution: {integrity: sha512-RkTN9EmgIUpLuhNoLhO3OliMGJlvb3Dk7XVjBZnfoMCHrVpuz5ESd97klfiyYeV7XfA2lNLnDCPMkUI+Qw5How==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2005' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -10459,6 +10597,63 @@ snapshots: '@oxc-project/types@0.150.0': {} + '@oxlint/binding-android-arm-eabi@1.90.0': + optional: true + + '@oxlint/binding-android-arm64@1.90.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.90.0': + optional: true + + '@oxlint/binding-darwin-x64@1.90.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.90.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.90.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.90.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.90.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.90.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.90.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.90.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.90.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.90.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.90.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.90.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.90.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.90.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.90.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.90.0': + optional: true + '@parcel/watcher-android-arm64@2.6.0': optional: true @@ -13642,6 +13837,28 @@ snapshots: '@oxc-parser/binding-win32-ia32-msvc': 0.142.0 '@oxc-parser/binding-win32-x64-msvc': 0.142.0 + oxlint@1.90.0: + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.90.0 + '@oxlint/binding-android-arm64': 1.90.0 + '@oxlint/binding-darwin-arm64': 1.90.0 + '@oxlint/binding-darwin-x64': 1.90.0 + '@oxlint/binding-freebsd-x64': 1.90.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.90.0 + '@oxlint/binding-linux-arm-musleabihf': 1.90.0 + '@oxlint/binding-linux-arm64-gnu': 1.90.0 + '@oxlint/binding-linux-arm64-musl': 1.90.0 + '@oxlint/binding-linux-ppc64-gnu': 1.90.0 + '@oxlint/binding-linux-riscv64-gnu': 1.90.0 + '@oxlint/binding-linux-riscv64-musl': 1.90.0 + '@oxlint/binding-linux-s390x-gnu': 1.90.0 + '@oxlint/binding-linux-x64-gnu': 1.90.0 + '@oxlint/binding-linux-x64-musl': 1.90.0 + '@oxlint/binding-openharmony-arm64': 1.90.0 + '@oxlint/binding-win32-arm64-msvc': 1.90.0 + '@oxlint/binding-win32-ia32-msvc': 1.90.0 + '@oxlint/binding-win32-x64-msvc': 1.90.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 diff --git a/scripts/lint-ratchet.d.mts b/scripts/lint-ratchet.d.mts new file mode 100644 index 00000000..fb096013 --- /dev/null +++ b/scripts/lint-ratchet.d.mts @@ -0,0 +1,9 @@ +export declare const LINT_PATHS: string[]; +export declare function countDiagnostics( + diagnostics: { filename: string; code?: string }[], + root?: string, +): Record>; +export declare function compareCounts( + baseline: Record>, + current: Record>, +): { added: string[]; fixed: string[] }; diff --git a/scripts/lint-ratchet.mjs b/scripts/lint-ratchet.mjs new file mode 100644 index 00000000..a4fca503 --- /dev/null +++ b/scripts/lint-ratchet.mjs @@ -0,0 +1,148 @@ +#!/usr/bin/env node +// Runs oxlint and fails when a file has more warnings of a rule than +// `lint-baseline.json` allows. The baseline only goes down: fix a warning and +// run `pnpm lint --update` to lower it, so the old count can't come back. +import { execFileSync } from 'node:child_process'; +import { readFileSync, realpathSync, writeFileSync } from 'node:fs'; +import { dirname, join, relative, sep } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); +const BASELINE = join(ROOT, 'lint-baseline.json'); +/** The folders oxlint checks, relative to the repository root. */ +export const LINT_PATHS = ['packages', 'app', 'apps', 'src', 'examples', 'scripts', 'e2e']; + +/** + * Counts oxlint's diagnostics per file and rule, with `/` paths relative to + * `root`, sorted so the baseline file diffs cleanly. + * @param {{ filename: string; code?: string }[]} diagnostics + * @param {string} root + * @returns {Record>} + */ +export function countDiagnostics(diagnostics, root = ROOT) { + /** @type {Record>} */ + const counts = {}; + for (const { filename, code } of diagnostics) { + const file = relative(root, join(root, filename)).split(sep).join('/'); + const rule = code ?? 'unknown'; + const rules = (counts[file] ??= {}); + rules[rule] = (rules[rule] ?? 0) + 1; + } + return sortCounts(counts); +} + +/** @param {Record>} counts */ +function sortCounts(counts) { + return Object.fromEntries( + Object.keys(counts) + .sort() + .map((file) => [ + file, + Object.fromEntries( + Object.keys(counts[file]) + .sort() + .map((rule) => [rule, counts[file][rule]]), + ), + ]), + ); +} + +/** + * Compares the counts with the baseline. + * @param {Record>} baseline + * @param {Record>} current + * @returns {{ added: string[]; fixed: string[] }} `added` lines fail the check; + * `fixed` lines mean the baseline can go down. + */ +export function compareCounts(baseline, current) { + const added = []; + const fixed = []; + for (const [file, rules] of Object.entries(current)) { + for (const [rule, count] of Object.entries(rules)) { + const allowed = baseline[file]?.[rule] ?? 0; + if (count > allowed) added.push(`${file}: ${rule} ${allowed} -> ${count}`); + } + } + for (const [file, rules] of Object.entries(baseline)) { + for (const [rule, allowed] of Object.entries(rules)) { + const count = current[file]?.[rule] ?? 0; + if (count < allowed) fixed.push(`${file}: ${rule} ${allowed} -> ${count}`); + } + } + return { added, fixed }; +} + +function runOxlint() { + const bin = join( + ROOT, + 'node_modules', + '.bin', + process.platform === 'win32' ? 'oxlint.cmd' : 'oxlint', + ); + let output; + try { + output = execFileSync(bin, ['-c', '.oxlintrc.json', '--format', 'json', ...LINT_PATHS], { + cwd: ROOT, + encoding: 'utf8', + maxBuffer: 64 * 1024 * 1024, + shell: process.platform === 'win32', + }); + } catch (error) { + // oxlint exits non-zero on errors, and still prints the report. + output = /** @type {{ stdout?: string }} */ (error).stdout ?? ''; + if (!output) throw error; + } + return JSON.parse(output).diagnostics ?? []; +} + +function readBaseline() { + try { + return JSON.parse(readFileSync(BASELINE, 'utf8')); + } catch { + return {}; + } +} + +function main(args) { + const diagnostics = runOxlint(); + const current = countDiagnostics(diagnostics); + const baseline = readBaseline(); + const { added, fixed } = compareCounts(baseline, current); + if (args.includes('--update')) { + if (added.length && !args.includes('--allow-new')) { + console.error('New lint warnings, so the baseline was not updated:\n' + added.join('\n')); + console.error('Fix them, or pass --allow-new when a reviewer agreed to keep them.'); + return 1; + } + writeFileSync(BASELINE, JSON.stringify(current, null, 2) + '\n'); + console.log( + `lint-baseline.json updated: ${diagnostics.length} warnings in ${Object.keys(current).length} files.`, + ); + return 0; + } + if (added.length) { + const lines = new Set(added.map((line) => line.split(':')[0])); + for (const d of diagnostics) { + const file = relative(ROOT, join(ROOT, d.filename)).split(sep).join('/'); + const label = d.labels?.[0]?.span; + if (lines.has(file)) + console.error( + `${file}${label ? `:${label.line}:${label.column}` : ''} ${d.code}: ${d.message}`, + ); + } + console.error('\nNew lint warnings (file: rule baseline -> now):\n' + added.join('\n')); + console.error('\nFix them; the baseline in lint-baseline.json only goes down.'); + return 1; + } + if (fixed.length) { + console.log( + `${fixed.length} counts are below the baseline. Run \`pnpm lint --update\` to lower it:\n` + + fixed.join('\n'), + ); + } + console.log(`oxlint: no new warnings (${diagnostics.length} in the baseline).`); + return 0; +} + +const invoked = process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url); +if (invoked) process.exitCode = main(process.argv.slice(2)); From 9aa475c9ba5713074cfe38e3eded5e67f336d534 Mon Sep 17 00:00:00 2001 From: erKam Date: Sat, 7 Nov 2026 17:40:26 +0000 Subject: [PATCH 02/32] refactor(overlay): type window.ng as NgDebugApi debug-api.ts declares the debug API Angular publishes, checked against @angular/core 22.1, with the first version of each function. The overlay hands it to the collectors instead of any, and the collectors keep their narrow interfaces. A type test checks that the two still fit, and a test against a real app fails when Angular publishes a function the type does not declare. Refs #373 --- .claude/skills/devtools-inspector/SKILL.md | 1 + docs/contributing/coding-standards.md | 2 +- lint-baseline.json | 3 - packages/devtools/CHANGELOG.md | 1 + .../src/__tests__/debug-api-real.test.ts | 38 ++++ .../devtools/src/__tests__/debug-api.test.ts | 36 ++++ packages/devtools/src/debug-api.ts | 172 ++++++++++++++++++ packages/devtools/src/errors-collector.ts | 8 +- .../devtools/src/overlay-angular-native.ts | 4 +- packages/devtools/src/overlay-nativescript.ts | 4 +- packages/devtools/src/overlay.ts | 5 +- packages/devtools/src/pipes-collector.ts | 8 +- packages/devtools/src/pipes-runtime.ts | 6 +- packages/devtools/vitest.compat.config.ts | 1 + 14 files changed, 268 insertions(+), 21 deletions(-) create mode 100644 packages/devtools/src/__tests__/debug-api-real.test.ts create mode 100644 packages/devtools/src/__tests__/debug-api.test.ts create mode 100644 packages/devtools/src/debug-api.ts diff --git a/.claude/skills/devtools-inspector/SKILL.md b/.claude/skills/devtools-inspector/SKILL.md index 989160b1..8b6fedd8 100644 --- a/.claude/skills/devtools-inspector/SKILL.md +++ b/.claude/skills/devtools-inspector/SKILL.md @@ -21,6 +21,7 @@ Read `docs/contributing/coding-standards.md` ("Reading data from the page") befo - Put collection logic in its own module (`-collector.ts`) and keep `overlay.ts` changes to wiring: import, attach, push, `leave()` and the returned cleanup. - Read Angular through the debug APIs on `window.ng` and verify each field against `node_modules/@angular/core/fesm2022` (or the library's fesm build). Known helpers: + - `debug-api.ts`: `NgDebugApi`, the typed `window.ng` with `@since` notes, and `pageDebugApi()`. Add a function there (and to `NG_DEBUG_FUNCTIONS`) before a collector reads it. - `element-id.ts`: stable `WeakMap` ids for elements (`elementId`, `elementById`). - `injector-tree.ts`: `className()` strips bundler `_` prefixes, `tokenName()`, `dependenciesOf()`, environment injector walk. - `serialize.ts`: safe, size-limited serialization. diff --git a/docs/contributing/coding-standards.md b/docs/contributing/coding-standards.md index d48a57af..e03a73a1 100644 --- a/docs/contributing/coding-standards.md +++ b/docs/contributing/coding-standards.md @@ -27,7 +27,7 @@ The panel's visual rules are in the [UI guidelines](./ui-guidelines.md). The overlay runs inside the user's app, so it must be correct, cheap and invisible. -- **Use Angular's debug APIs**, not guesses: `ng.getComponent`, `ng.getDirectives`, `ng.getDirectiveMetadata`, `ng.getListeners`, `ng.getInjector`, `ng.ɵgetInjectorProviders`, `ng.ɵgetInjectorResolutionPath`, `ng.ɵgetDependenciesFromInjectable`, `ng.ɵgetSignalGraph`. Check the shapes in `node_modules/@angular/core/fesm2022` before relying on a field. +- **Use Angular's debug APIs**, not guesses: `ng.getComponent`, `ng.getDirectives`, `ng.getDirectiveMetadata`, `ng.getListeners`, `ng.getInjector`, `ng.ɵgetInjectorProviders`, `ng.ɵgetInjectorResolutionPath`, `ng.ɵgetDependenciesFromInjectable`, `ng.ɵgetSignalGraph`. Check the shapes in `node_modules/@angular/core/fesm2022` before relying on a field. The overlay hands collectors `window.ng` typed as `NgDebugApi` (`debug-api.ts`, with the version each function arrived in); a collector declares the narrow part it reads, and `debug-api.test.ts` checks that the two still fit. - **Never write to the app's DOM.** Give elements and objects ids through a `WeakMap` (`element-id.ts`, `idFor` in `injector-tree.ts`), so ids stay stable between pushes and selection survives a refresh. - **Walk the host tree, not the DOM**, in the component, injector, signal graph and NgRx collectors. Take a `HostTree` (`host-tree.ts`) that defaults to `domTree()`, so the same collector runs on a platform without a DOM. - **Strip bundler prefixes** from class names (`className()` in `injector-tree.ts`), so `_App` shows as `App`. diff --git a/lint-baseline.json b/lint-baseline.json index 31d875da..696cbf87 100644 --- a/lint-baseline.json +++ b/lint-baseline.json @@ -271,9 +271,6 @@ "packages/devtools/src/overlay-link.ts": { "typescript(no-explicit-any)": 1 }, - "packages/devtools/src/overlay.ts": { - "typescript(no-explicit-any)": 2 - }, "packages/devtools/src/page-lease.ts": { "typescript(no-explicit-any)": 1 }, diff --git a/packages/devtools/CHANGELOG.md b/packages/devtools/CHANGELOG.md index 51cb481f..5016d02b 100644 --- a/packages/devtools/CHANGELOG.md +++ b/packages/devtools/CHANGELOG.md @@ -241,6 +241,7 @@ What remains: `/config` exports `DEFAULT_POPUP_HOTKEY`, `PANGULAR_ACTIONS`, `PAN ### Release and CI +- The overlay reads `window.ng` through one typed `NgDebugApi` (`debug-api.ts`) with the Angular version of each function, and a test against a real app fails when Angular publishes a function the type doesn't declare (#373). - `pnpm lint` runs oxlint with a ratchet: CI fails when a file gets more warnings of a rule than `lint-baseline.json` allows (#373). - The `leaks-real` test expects the rows the collector really reports (#430). - Pull requests run the security and MCP end-to-end checks, and a weekly job runs the rest. diff --git a/packages/devtools/src/__tests__/debug-api-real.test.ts b/packages/devtools/src/__tests__/debug-api-real.test.ts new file mode 100644 index 00000000..7745a304 --- /dev/null +++ b/packages/devtools/src/__tests__/debug-api-real.test.ts @@ -0,0 +1,38 @@ +// @vitest-environment jsdom +import '@angular/compiler'; +import { Component, type ApplicationRef } from '@angular/core'; +import { bootstrapApplication } from '@angular/platform-browser'; +import { afterEach, describe, expect, it } from 'vitest'; +import { NG_DEBUG_FUNCTIONS, NG_DEBUG_REQUIRED, pageDebugApi } from '../debug-api.ts'; +import { STANDALONE, angularAtLeast, zonelessProviders } from './angular-version.ts'; + +class DebugRoot {} +Component({ ...STANDALONE, selector: 'app-root', template: '

ok

' })(DebugRoot); + +describe('NgDebugApi against a real app', () => { + let app: ApplicationRef | null = null; + afterEach(() => { + app?.destroy(); + app = null; + document.body.innerHTML = ''; + }); + + it('has every required function, and declares every function Angular publishes', async () => { + document.body.innerHTML = ''; + app = await bootstrapApplication(DebugRoot, { providers: [...zonelessProviders()] }); + const ng = pageDebugApi(); + expect(ng).toBeDefined(); + for (const name of NG_DEBUG_REQUIRED) expect(typeof ng![name], name).toBe('function'); + const published = Object.keys(ng!).filter( + (name) => typeof (ng as unknown as Record)[name] === 'function', + ); + // A function Angular adds or renames shows up here, so the type follows it. The + // list was checked on 22.1; older versions in the compat run check the required part. + const known: readonly string[] = NG_DEBUG_FUNCTIONS; + if (angularAtLeast('22.0')) + expect(published.filter((name) => !known.includes(name))).toEqual([]); + const root = document.querySelector('app-root')!; + expect(ng!.getComponent(root)).toBeInstanceOf(DebugRoot); + expect(ng!.getHostElement(ng!.getComponent(root)!)).toBe(root); + }); +}); diff --git a/packages/devtools/src/__tests__/debug-api.test.ts b/packages/devtools/src/__tests__/debug-api.test.ts new file mode 100644 index 00000000..9e5409ec --- /dev/null +++ b/packages/devtools/src/__tests__/debug-api.test.ts @@ -0,0 +1,36 @@ +import { afterEach, describe, expect, expectTypeOf, it } from 'vitest'; +import { pageDebugApi, type NgDebugApi } from '../debug-api.ts'; +import type { ComponentDebugNg } from '../component-tree.ts'; +import type { DebugNg as InjectorDebugNg } from '../injector-tree.ts'; +import type { SignalDebugNg } from '../signal-graph.ts'; +import type { ErrorsDebugNg } from '../errors-collector.ts'; +import type { DeferDebugNg } from '../defer-blocks.ts'; + +const g = globalThis as { ng?: unknown }; + +describe('pageDebugApi', () => { + afterEach(() => { + delete g.ng; + }); + + it('is undefined without a debug API, or when ng is not an object', () => { + expect(pageDebugApi()).toBeUndefined(); + g.ng = 'production'; + expect(pageDebugApi()).toBeUndefined(); + }); + + it('hands back window.ng as it is', () => { + const ng = { getComponent: () => null }; + g.ng = ng; + expect(pageDebugApi()).toBe(ng); + }); + + it('fits what the collectors ask for', () => { + // A field typed differently here and in a collector fails `tsc`. + expectTypeOf().toExtend>(); + expectTypeOf().toExtend>(); + expectTypeOf().toExtend>(); + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); + }); +}); diff --git a/packages/devtools/src/debug-api.ts b/packages/devtools/src/debug-api.ts new file mode 100644 index 00000000..9554088b --- /dev/null +++ b/packages/devtools/src/debug-api.ts @@ -0,0 +1,172 @@ +// The shape of `window.ng`, the debug API Angular publishes in development +// builds. Checked against `publishDefaultGlobalUtils` in +// `@angular/core/fesm2022/_debug_node-chunk.mjs` (22.1) and the router's +// `_publishNonCoreGlobalUtil` calls. Each `@since` names the first Angular +// version that publishes the function; `ng-capabilities.ts` checks the page +// at runtime, and `ANGULAR_FEATURE_NEEDS` in `config.ts` words what is missing. +// Functions published by every version the overlay supports (12.0+, the +// `debugApi` capability) are required; later ones are optional. +// +// Collectors keep their own narrow interfaces (`ComponentDebugNg`, +// `HydrationScanNg` and so on) so they stay testable with a small fake `ng`; +// this type is what the overlay hands them, so a renamed or retyped function +// shows up as a type error at the call site. + +/** A listener as `ng.getListeners` returns it. */ +export interface NgDebugListener { + name: string; + element: Element; + callback: (value: unknown) => unknown; + useCapture: boolean; + type: 'dom' | 'output'; +} + +/** What `ng.getDirectiveMetadata` returns for a component or directive instance. */ +export interface NgDebugDirectiveMetadata { + inputs?: Record; + outputs?: Record; + encapsulation?: number; + changeDetection?: number; + [key: string]: unknown; +} + +/** What `ng.ɵgetInjectorMetadata` returns. */ +export interface NgDebugInjectorMetadata { + /** `element`, `environment` or `null` in 22.1. */ + type: string; + /** The host element, the environment injector's name, or `null`. */ + source: unknown; +} + +/** What `ng.ɵgetDependenciesFromInjectable` returns for a created token. */ +export interface NgDebugDependencies { + instance?: unknown; + dependencies: { + token?: unknown; + value?: unknown; + flags?: { optional?: boolean; host?: boolean; self?: boolean; skipSelf?: boolean }; + /** The injector the dependency resolved from. */ + providedIn?: unknown; + }[]; +} + +/** One entry of `ng.ɵgetInjectorProviders`. */ +export interface NgDebugProviderRecord { + token: unknown; + provider: unknown; + /** Element injectors only. */ + isViewProvider?: boolean; + /** Environment injectors only: the imports that brought the provider in. */ + importPath?: unknown[]; +} + +/** A node of `ng.ɵgetSignalGraph`; `id` exists from 20.1. */ +export interface NgDebugSignalNode { + id?: string; + kind?: string; + label?: string; + epoch?: number; + value?: unknown; + debuggableFn?: unknown; +} + +/** What `ng.ɵgetSignalGraph` returns: edges index into `nodes`. */ +export interface NgDebugSignalGraph { + nodes: NgDebugSignalNode[]; + edges?: { consumer: number; producer: number }[]; +} + +export interface NgDebugApi { + /** The component whose host is `element`, or `null`. @since 12.0 */ + getComponent(element: Element): T | null; + /** The template context of an embedded view, or the component. @since 12.0 */ + getContext(element: Element): T | null; + /** The component whose template holds `elementOrDir`. @since 12.0 */ + getOwningComponent(elementOrDir: Element | object): T | null; + /** The root components of the app that holds `elementOrDir`. @since 12.0 */ + getRootComponents(elementOrDir: Element | object): object[]; + /** The element injector (`Injector.NULL` outside Angular). @since 12.0 */ + getInjector(elementOrDir: Element | object): unknown; + /** Directives on a node, components included. @since 12.0 */ + getDirectives(node: Node): object[]; + /** Inputs, outputs, encapsulation and change detection of an instance. @since 12.0 */ + getDirectiveMetadata(instance: object): NgDebugDirectiveMetadata | null; + /** The host element of a component or directive instance. @since 12.0 */ + getHostElement(componentOrDirective: object): Element; + /** DOM and output listeners of an element. @since 12.0 */ + getListeners(element: Element): NgDebugListener[]; + /** Marks a component for check and runs change detection. @since 12.0 */ + applyChanges(component: object): void; + /** Whether a value is a signal. @since 17.1 */ + isSignal?(value: unknown): boolean; + /** Turns on Angular's own Chrome Performance track and hands back the stop function. @since 20.0 */ + enableProfiling?(): () => void; + /** Dependencies a token injected from `injector`. @since 16.2 */ + ɵgetDependenciesFromInjectable?(injector: unknown, token: unknown): NgDebugDependencies; + /** Providers registered on an injector. @since 16.2 */ + ɵgetInjectorProviders?(injector: unknown): NgDebugProviderRecord[]; + /** The injectors a lookup walks from `injector` up. @since 16.2 */ + ɵgetInjectorResolutionPath?(injector: unknown): unknown[]; + /** Type (`element`, `environment` or `null`) and source of an injector. @since 17.0 */ + ɵgetInjectorMetadata?(injector: unknown): NgDebugInjectorMetadata | null; + /** Adds a profiler. From 20.0 it hands back its remover; before that it has one slot (`ng-capabilities.ts` checks the version). */ + ɵsetProfiler?( + profiler: ((event: number, instance?: unknown, context?: unknown) => void) | null, + ): unknown; + /** The reactive graph of an injector. Node ids from 20.1. @since 19.1 */ + ɵgetSignalGraph?(injector: unknown): NgDebugSignalGraph | null; + /** `@defer` blocks under a node. Replaced by `ɵgetControlFlowBlocks` in 22.0. @since 19.1 */ + ɵgetDeferBlocks?(node: Node): unknown[]; + /** `@defer` and `@for` blocks under a node. @since 22.0 */ + ɵgetControlFlowBlocks?(node: Node): unknown[]; + /** The TransferState entries without Angular's own hydration keys. Present in 22.1; not checked on older versions. */ + ɵgetTransferState?(injector: unknown): Record; + /** The id a component instance has in Angular DevTools deep links. Present in 22.1; not checked on older versions. */ + ɵgetComponentInstanceDeepLinkId?(instance: object): number | undefined; + /** The router of the app, published by `provideRouter()`. @since 20.3 */ + ɵgetRouterInstance?(injector: unknown): unknown; + /** Router-loaded routes of a route config. @since 20.3 */ + ɵgetLoadedRoutes?(route: unknown): unknown; + /** Navigates the router of an injector. @since 20.3 */ + ɵnavigateByUrl?(router: unknown, url: string): unknown; +} + +/** Functions every supported version publishes (the `debugApi` capability floor). */ +export const NG_DEBUG_REQUIRED = [ + 'getComponent', + 'getContext', + 'getOwningComponent', + 'getRootComponents', + 'getInjector', + 'getDirectives', + 'getDirectiveMetadata', + 'getHostElement', + 'getListeners', + 'applyChanges', +] as const satisfies readonly (keyof NgDebugApi)[]; + +/** Every function {@link NgDebugApi} declares, for the canary test against a real app. */ +export const NG_DEBUG_FUNCTIONS = [ + ...NG_DEBUG_REQUIRED, + 'isSignal', + 'enableProfiling', + 'ɵgetDependenciesFromInjectable', + 'ɵgetInjectorProviders', + 'ɵgetInjectorResolutionPath', + 'ɵgetInjectorMetadata', + 'ɵsetProfiler', + 'ɵgetSignalGraph', + 'ɵgetDeferBlocks', + 'ɵgetControlFlowBlocks', + 'ɵgetTransferState', + 'ɵgetComponentInstanceDeepLinkId', + 'ɵgetRouterInstance', + 'ɵgetLoadedRoutes', + 'ɵnavigateByUrl', +] as const satisfies readonly (keyof NgDebugApi)[]; + +/** `window.ng` when the page is an Angular development build, else `undefined`. */ +export function pageDebugApi(): NgDebugApi | undefined { + const ng = (globalThis as { ng?: unknown }).ng; + return ng && typeof ng === 'object' ? (ng as NgDebugApi) : undefined; +} diff --git a/packages/devtools/src/errors-collector.ts b/packages/devtools/src/errors-collector.ts index 992baa65..11f34dbc 100644 --- a/packages/devtools/src/errors-collector.ts +++ b/packages/devtools/src/errors-collector.ts @@ -37,10 +37,10 @@ type AnyRecord = Record; /** The parts of `window.ng` this collector reads. */ export interface ErrorsDebugNg extends RouterDebugApi, HydrationDebugNg { - getComponent?: (el: Element) => unknown; - getOwningComponent?: (el: Element) => unknown; - getHostElement?: (component: unknown) => Element | null; - getInjector?: (el: Element) => unknown; + getComponent?(el: Element): unknown; + getOwningComponent?(el: Element): unknown; + getHostElement?(component: unknown): Element | null; + getInjector?(el: Element): unknown; } interface Rpc { diff --git a/packages/devtools/src/overlay-angular-native.ts b/packages/devtools/src/overlay-angular-native.ts index d56cacee..2752d06f 100644 --- a/packages/devtools/src/overlay-angular-native.ts +++ b/packages/devtools/src/overlay-angular-native.ts @@ -14,7 +14,7 @@ import { attachNativeDiagnostics, errorUtilsTarget, nativeDebugApi } from './nat import { attachNgrx } from './ngrx-overlay.ts'; import { installReduxDevtoolsHook, uninstallReduxDevtoolsHook } from './redux-devtools-hook.ts'; import { attachPipes } from './pipes-collector.ts'; -import type { NgDebugApi } from './pipes-runtime.ts'; +import type { PipeDebugNg } from './pipes-runtime.ts'; import { angularNativeTree, clearOutline, @@ -343,7 +343,7 @@ async function startSession( : null; const pipes = on.pipes - ? attachPipes(my, pageId, () => angularDebugApi() as NgDebugApi | undefined, { tree }) + ? attachPipes(my, pageId, () => angularDebugApi() as PipeDebugNg | undefined, { tree }) : null; const collectors = [ diff --git a/packages/devtools/src/overlay-nativescript.ts b/packages/devtools/src/overlay-nativescript.ts index 1eaaf780..3aa6743e 100644 --- a/packages/devtools/src/overlay-nativescript.ts +++ b/packages/devtools/src/overlay-nativescript.ts @@ -28,7 +28,7 @@ import { attachNgrx } from './ngrx-overlay.ts'; import { installReduxDevtoolsHook, uninstallReduxDevtoolsHook } from './redux-devtools-hook.ts'; import { HIGHLIGHT_SAFETY_MS } from './page-highlight.ts'; import { attachPipes } from './pipes-collector.ts'; -import type { NgDebugApi } from './pipes-runtime.ts'; +import type { PipeDebugNg } from './pipes-runtime.ts'; import { collectSignalGraph, graphKey, @@ -361,7 +361,7 @@ async function startSession( : null; const pipes = on.pipes - ? attachPipes(my, pageId, () => angularDebugApi() as NgDebugApi | undefined, { tree }) + ? attachPipes(my, pageId, () => angularDebugApi() as PipeDebugNg | undefined, { tree }) : null; const collectors = [ diff --git a/packages/devtools/src/overlay.ts b/packages/devtools/src/overlay.ts index ff069f9e..8a57dc98 100644 --- a/packages/devtools/src/overlay.ts +++ b/packages/devtools/src/overlay.ts @@ -103,6 +103,7 @@ import { } from './change-detection.ts'; import { attachChangeDetection } from './cd-overlay.ts'; import { attachAngularInfo } from './ng-capabilities.ts'; +import { pageDebugApi, type NgDebugApi } from './debug-api.ts'; import { createScanBackoff } from './router-attach.ts'; import { DEVTOOLS_BASES, SETUP_URL } from './panel-frame.ts'; import { overlayStarts } from './overlay-start.ts'; @@ -1175,6 +1176,6 @@ function read(fn: () => T, fallback: T): T { } } -function getNg(): any { - return (window as any).ng; +function getNg(): NgDebugApi | undefined { + return pageDebugApi(); } diff --git a/packages/devtools/src/pipes-collector.ts b/packages/devtools/src/pipes-collector.ts index 1765bcdf..9ef82efd 100644 --- a/packages/devtools/src/pipes-collector.ts +++ b/packages/devtools/src/pipes-collector.ts @@ -10,7 +10,7 @@ import { scanPipeViews, staleCheckFor, stripBundlerPrefix, - type NgDebugApi, + type PipeDebugNg, type PipeCall, type PipeInstrumentation, type PipeUsage, @@ -323,7 +323,7 @@ function hostsOf(tree: HostTree): H[] { export function attachPipes( my: Rpc, pageId: string, - getNg: () => NgDebugApi | undefined, + getNg: () => PipeDebugNg | undefined, options: PipesOptions = {}, ): PipesCollector { const tree = options.tree; @@ -367,7 +367,7 @@ export function attachPipes( : null; observer?.observe(document.documentElement, { childList: true, subtree: true }); - function discover(ng: NgDebugApi): PipeUsage[] { + function discover(ng: PipeDebugNg): PipeUsage[] { if (tree) { usages = scanPipeViews(ng, hostsOf(tree) as unknown as Element[]).usages; return usages; @@ -400,7 +400,7 @@ export function attachPipes( return usages; } - function targetOf(ng: NgDebugApi): (component: unknown) => PipeTarget | undefined { + function targetOf(ng: PipeDebugNg): (component: unknown) => PipeTarget | undefined { const cache = new Map(); return (component) => { if (cache.has(component)) return cache.get(component); diff --git a/packages/devtools/src/pipes-runtime.ts b/packages/devtools/src/pipes-runtime.ts index 54fbfe43..cb7fc406 100644 --- a/packages/devtools/src/pipes-runtime.ts +++ b/packages/devtools/src/pipes-runtime.ts @@ -2,7 +2,7 @@ import { callerFrom } from './forms-instrument.ts'; type AnyRecord = Record; -export interface NgDebugApi { +export interface PipeDebugNg { getComponent(el: Element): unknown; getOwningComponent?(el: Element): unknown; getDirectives?(el: Element): unknown[]; @@ -173,7 +173,7 @@ export interface PipeScan { * embedded views, so views holding only text nodes (which Angular never * patches with `__ngContext__`) are found too. Usages are attributed to the * component whose template declares them. */ -export function scanPipeViews(ng: NgDebugApi, elements: Iterable): PipeScan { +export function scanPipeViews(ng: PipeDebugNg, elements: Iterable): PipeScan { const seen = new Set(); const usages: PipeUsage[] = []; const entries: Element[] = []; @@ -208,7 +208,7 @@ export function scanPipeViews(ng: NgDebugApi, elements: Iterable): Pipe return { usages, entries }; } -export function findPipeUsages(ng: NgDebugApi, elements: Iterable): PipeUsage[] { +export function findPipeUsages(ng: PipeDebugNg, elements: Iterable): PipeUsage[] { return scanPipeViews(ng, elements).usages; } diff --git a/packages/devtools/vitest.compat.config.ts b/packages/devtools/vitest.compat.config.ts index d5ec769f..fd465941 100644 --- a/packages/devtools/vitest.compat.config.ts +++ b/packages/devtools/vitest.compat.config.ts @@ -15,6 +15,7 @@ const pageSide = [ 'cd-recorder-real', 'change-detection', 'change-detection-real', + 'debug-api-real', 'component-tree', 'console-tap', 'defer-blocks', From af5a5285083e71f9a61f1bbe587b54011b57ce66 Mon Sep 17 00:00:00 2001 From: erKam Date: Sat, 7 Nov 2026 17:44:37 +0000 Subject: [PATCH 03/32] feat(hub): mount extra devframes with extraDevframes initPangularHub() and the Vite plugin take extraDevframes, the same input as initHub({ devframes }), and mount them after Pangular Inspector behind the same gate and one-time code. An entry with the pangular id is dropped with a warning, so it can't replace the inspector. Refs #373 --- .../content/getting-started/configuration.md | 13 +++++ .../src/content/getting-started/express.md | 3 +- apps/docs/src/content/getting-started/vite.md | 19 +++---- packages/devtools/CHANGELOG.md | 2 +- packages/devtools/src/__tests__/hub.test.ts | 52 ++++++++++++++++++- .../__tests__/vite-extra-devframes.test.ts | 35 +++++++++++++ packages/devtools/src/hub.ts | 46 +++++++++++++++- packages/devtools/src/vite.ts | 8 +++ 8 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 packages/devtools/src/__tests__/vite-extra-devframes.test.ts diff --git a/apps/docs/src/content/getting-started/configuration.md b/apps/docs/src/content/getting-started/configuration.md index b9ce5f84..2bdaf082 100644 --- a/apps/docs/src/content/getting-started/configuration.md +++ b/apps/docs/src/content/getting-started/configuration.md @@ -265,6 +265,19 @@ The server plugins themselves go in `serverPlugins`, an option of the Express hu It is off by default because it starts processes on your machine. A value other than `true` or `false` prints a warning and keeps it off. +### Extra devframes + +`extraDevframes` is an option of the Express hub and the Vite plugin only. It mounts other devframes on the hub that serves Pangular Inspector, so a team's own devtools or another library's devframe sit next to the Pangular docks without a second server. It takes the same input as `initHub({ devframes })` from `@devframes/hub`: devframe definitions, `{ devframe, dock }` entries to change a dock, promises and functions that return them. + +```ts +import {initPangularHub} from '@pangular-inspector/devtools/hub'; +import {acmeDevframe} from '@acme/devtools'; + +const devtools = initPangularHub({extraDevframes: [acmeDevframe]}); +``` + +The extra devframes run behind the same origin check, request gate and one-time code as Pangular Inspector. An entry with the id `pangular` would replace Pangular Inspector, so it is dropped with a warning, and a value that is not an array is ignored with a warning. + ### Lint rules | Option | Default | What it does | diff --git a/apps/docs/src/content/getting-started/express.md b/apps/docs/src/content/getting-started/express.md index 7a6fd944..cfbb562f 100644 --- a/apps/docs/src/content/getting-started/express.md +++ b/apps/docs/src/content/getting-started/express.md @@ -111,7 +111,7 @@ devtools.attach(server); ### Hub options -`initPangularHub()` accepts the options of `initHub()` from `@devframes/hub`, apart from `devframes` and `ui`. These are the ones you are most likely to set: +`initPangularHub()` accepts the options of `initHub()` from `@devframes/hub`, apart from `devframes` and `ui`; pass other devframes in `extraDevframes`. These are the ones you are most likely to set: | Option | Default | What it does | | ---------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -122,6 +122,7 @@ devtools.attach(server); | `allowedOrigins` | loopback origins and the Chrome extension | Extra origins allowed to reach the hub, such as a tunnel. `false` turns the origin check and the request gate off. | | `allowLan` | `false` | Answer other machines, such as a phone on Wi-Fi. Needs the one-time code on. | | `terminals` | `false` | Add a dock that runs the project's build and test commands. See [Terminals](./terminals.md). | +| `extraDevframes` | none | Other devframes to mount on the same hub, after Pangular Inspector, the same input as `initHub({ devframes })`. See [Extra devframes](./configuration.md#extra-devframes). | | `mcp` | a bearer token | Mounts the MCP endpoint at `__mcp` and asks for a bearer token. With `auth: false` the default is `'auto'`: it mounts once agent tools exist and asks for no token. See [Send a token](../agents/mcp-server.md#send-a-token). | The hub also takes the devtools options, such as `inspectors`, `agent`, `actions`, `redaction` and `limits`. See [Configuration](./configuration.md). Pass `browserDistFolder`, the folder that holds `index.csr.html`, to let the **Force Client render** override serve it. See [SSR overrides](../inspectors/ssr-http.md#ssr-overrides). diff --git a/apps/docs/src/content/getting-started/vite.md b/apps/docs/src/content/getting-started/vite.md index 194c3e30..af20e1e3 100644 --- a/apps/docs/src/content/getting-started/vite.md +++ b/apps/docs/src/content/getting-started/vite.md @@ -93,15 +93,16 @@ pangular({ }); ``` -| Option | Default | What it does | -| ---------------- | -------------------------------- | ---------------------------------------------------------------------------------- | -| `base` | `'/__devframes/'` | Where the hub is mounted. | -| `apiPrefix` | Analog's `apiPrefix`, or `'api'` | The prefix of your server routes, used to classify API calls. | -| `allowedOrigins` | none | Extra exact origins allowed to reach the devtools, for example a tunnel. | -| `auth` | on | Whether the devtools ask for the one-time code, and its pairing tokens. | -| `allowLan` | `false` | Answer other machines, such as a phone on Wi-Fi, from listed origins. | -| `overlay` | `true` | Add the overlay to the pages the dev server serves. | -| `terminals` | `false` | Add a dock that runs the build and test commands. See [Terminals](./terminals.md). | +| Option | Default | What it does | +| ---------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `base` | `'/__devframes/'` | Where the hub is mounted. | +| `apiPrefix` | Analog's `apiPrefix`, or `'api'` | The prefix of your server routes, used to classify API calls. | +| `allowedOrigins` | none | Extra exact origins allowed to reach the devtools, for example a tunnel. | +| `auth` | on | Whether the devtools ask for the one-time code, and its pairing tokens. | +| `allowLan` | `false` | Answer other machines, such as a phone on Wi-Fi, from listed origins. | +| `overlay` | `true` | Add the overlay to the pages the dev server serves. | +| `terminals` | `false` | Add a dock that runs the build and test commands. See [Terminals](./terminals.md). | +| `extraDevframes` | none | Other devframes to mount on the same hub, behind the same gate. See [Extra devframes](./configuration.md#extra-devframes). | The plugin also takes the devtools options, such as `inspectors`, `agent`, `actions`, `redaction` and `limits`. See [Configuration](./configuration.md). diff --git a/packages/devtools/CHANGELOG.md b/packages/devtools/CHANGELOG.md index 5016d02b..86c39dcd 100644 --- a/packages/devtools/CHANGELOG.md +++ b/packages/devtools/CHANGELOG.md @@ -126,7 +126,7 @@ What remains: `/config` exports `DEFAULT_POPUP_HOTKEY`, `PANGULAR_ACTIONS`, `PAN #### Setup and workflow - The Vite plugin loads the overlay itself, so setup is one line in `vite.config.ts`. New plugin options: `allowLan`, `overlay`, `terminals` and `serverPlugins`. -- New hub options: `enabled`, `allowLan`, `terminals`, `serverPlugins` and `browserDistFolder`. +- New hub options: `enabled`, `allowLan`, `terminals`, `serverPlugins`, `extraDevframes` and `browserDistFolder`. The Vite plugin takes `extraDevframes` too, to mount other devframes on the same hub (#373). - Open in editor: Alt+click a component on the page, or a file and line in the panel. - A command palette (⌘K or Ctrl+K) with a command for every tab and action. - A **Settings** page: theme, hidden, pinned and reordered tabs, passive mode, and a **Browser agents** switch. diff --git a/packages/devtools/src/__tests__/hub.test.ts b/packages/devtools/src/__tests__/hub.test.ts index ad685e22..fb0ce265 100644 --- a/packages/devtools/src/__tests__/hub.test.ts +++ b/packages/devtools/src/__tests__/hub.test.ts @@ -5,7 +5,13 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { Hono } from 'hono'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import { hubDefaultOrigins, initPangularHub, type PangularHubOptions } from '../hub.ts'; +import { defineDevframe } from 'devframe'; +import { + extraDevframesOf, + hubDefaultOrigins, + initPangularHub, + type PangularHubOptions, +} from '../hub.ts'; import { httpRegistry } from '../http-rules.ts'; import { writeTokenOnce } from '../mcp-token.ts'; import { BASE_FILES, makeProject } from './analog-fixture.ts'; @@ -265,6 +271,50 @@ async function sseStatus( return res.status; } +describe('Pangular Inspector hub extraDevframes', () => { + const extra = (id: string) => + defineDevframe({ + id, + name: `Extra ${id}`, + version: '1.0.0', + packageName: `@acme/${id}`, + homepage: 'https://example.com', + description: 'An extra devframe', + setup(ctx) { + ctx.rpc.register({ name: `${id}:ping`, type: 'static', handler: () => 'pong' }); + }, + }); + + it('mounts extra devframes next to Pangular Inspector', async () => { + const cwd = makeProject({ 'package.json': '{}' }); + const hub = initPangularHub({ + cwd, + ws: false, + auth: false, + allowedOrigins: false, + extraDevframes: [extra('acme'), { devframe: extra('beta') }], + }); + hubs.push(hub); + await hub.ready; + const ctx = await hub.context; + expect(ctx.rpc.has('acme:ping')).toBe(true); + expect(ctx.rpc.has('beta:ping')).toBe(true); + // Pangular Inspector is still there, and its tools still answer. + expect((await ctx.agent.invoke('pangular:inspect-forms', {})) as string).toContain('No forms'); + }); + + it('drops an entry with the pangular id and anything that is not an array', () => { + const warn = vi.fn(); + const own = extra('pangular'); + const other = extra('acme'); + expect(extraDevframesOf([own, other, { devframe: own }], warn)).toEqual([other]); + expect(warn).toHaveBeenCalledTimes(2); + expect(extraDevframesOf({} as never, warn)).toEqual([]); + expect(extraDevframesOf(undefined, warn)).toEqual([]); + expect(warn).toHaveBeenCalledTimes(3); + }); +}); + describe('Pangular Inspector hub behind a web router', () => { it('answers under its base and lets other routes reach the app', async () => { const cwd = makeProject({ 'package.json': '{}' }); diff --git a/packages/devtools/src/__tests__/vite-extra-devframes.test.ts b/packages/devtools/src/__tests__/vite-extra-devframes.test.ts new file mode 100644 index 00000000..d8ce4491 --- /dev/null +++ b/packages/devtools/src/__tests__/vite-extra-devframes.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it, vi } from 'vitest'; + +const hubOptions = vi.hoisted(() => [] as Record[]); + +vi.mock('../hub.ts', () => ({ + PANGULAR_HUB_BASE: '/__devframes/', + startPangularHub: (options: Record) => { + hubOptions.push(options); + return { nodeMiddleware: vi.fn(), close: vi.fn() }; + }, +})); + +const { default: pangularVite } = await import('../vite.ts'); + +function startedWith(options: Parameters[0]) { + hubOptions.length = 0; + const plugin = pangularVite({ apiPrefix: 'api', ...options }); + (plugin.configureServer as (server: unknown) => void)({ + config: { root: process.cwd(), server: {}, logger: { warn: vi.fn(), info: vi.fn() } }, + middlewares: { use: vi.fn() }, + httpServer: null, + }); + return hubOptions[0]!; +} + +describe('Vite plugin extraDevframes option', () => { + it('passes the extra devframes to the hub as given', () => { + const extra = [{ devframe: { id: 'acme' } }] as never; + expect(startedWith({ extraDevframes: extra })['extraDevframes']).toBe(extra); + }); + + it('passes nothing when the option is left out', () => { + expect(startedWith({})['extraDevframes']).toBeUndefined(); + }); +}); diff --git a/packages/devtools/src/hub.ts b/packages/devtools/src/hub.ts index 91a12f69..c41e0c3d 100644 --- a/packages/devtools/src/hub.ts +++ b/packages/devtools/src/hub.ts @@ -4,7 +4,7 @@ import { dirname, join } from 'node:path'; import { createUi } from '@devframes/hub-ui'; import { DEVFRAMES_HUB_BASE, initHub } from '@devframes/hub/initiate'; import { normalizeHubBase } from '@devframes/hub/constants'; -import type { InitHubOptions } from '@devframes/hub/initiate'; +import type { DevframesInput, InitHubOptions } from '@devframes/hub/initiate'; import type { WsOriginRegistry } from 'devframe/rpc/transports/ws-server'; import { isAllowedOrigin } from 'devframe/utils/origin'; import { createPangular } from './devframe.ts'; @@ -66,6 +66,12 @@ export type PangularHubOptions = Partial void = (message) => console.warn(message), +): DevframesInput { + if (input === undefined) return []; + if (!Array.isArray(input)) { + warn('[pangular] Ignoring extraDevframes: pass an array, as initHub({ devframes }) takes.'); + return []; + } + return input.filter((entry) => { + const id = devframeIdOf(entry); + if (id !== 'pangular') return true; + warn( + '[pangular] Ignoring an extraDevframes entry with the id "pangular": the hub mounts Pangular Inspector itself.', + ); + return false; + }); +} + +/** The id of a devframe or a `{ devframe }` entry; `undefined` for lazy entries. */ +function devframeIdOf(entry: unknown): string | undefined { + if (!entry || typeof entry !== 'object') return undefined; + const definition = 'devframe' in entry ? (entry as { devframe: unknown }).devframe : entry; + const id = + definition && typeof definition === 'object' ? (definition as { id?: unknown }).id : undefined; + return typeof id === 'string' ? id : undefined; +} + const REFUSED_BODY = 'Pangular Inspector only answers requests from this machine.'; const EXPRESS_LAN_FIX = 'To answer other machines, pass allowLan: true and keep auth on.'; diff --git a/packages/devtools/src/vite.ts b/packages/devtools/src/vite.ts index a2b5fe77..81c25890 100644 --- a/packages/devtools/src/vite.ts +++ b/packages/devtools/src/vite.ts @@ -1,6 +1,7 @@ import { resolve } from 'node:path'; import { isAuthTokens, type PangularAuthTokens } from './auth-tokens.ts'; import type { Plugin } from 'vite'; +import type { DevframesInput } from '@devframes/hub/initiate'; import { normalizeHubBase } from '@devframes/hub/constants'; import type { WsOriginRegistry } from 'devframe/rpc/transports/ws-server'; import { PANGULAR_HUB_BASE, startPangularHub } from './hub.ts'; @@ -59,6 +60,12 @@ export interface PangularViteOptions extends PangularConfig { terminals?: boolean; /** Pangular plugins' server sides: their agent tools and shared state. */ serverPlugins?: readonly PangularServerPlugin[]; + /** + * Other devframes to mount on the same hub, after Pangular Inspector: the + * same input as `initHub({ devframes })`. Each gets its own dock and runs + * behind the plugin's request gate and one-time code. + */ + extraDevframes?: DevframesInput; } export { @@ -175,6 +182,7 @@ export default function pangularVite(options: PangularViteOptions = {}): Plugin logger?.warn(message), ), serverPlugins: options.serverPlugins, + extraDevframes: options.extraDevframes, }); httpServer?.on('upgrade', hubUpgradeListener(base, policy, devtools.handleUpgrade, blocked)); server.middlewares.use(devtools.nodeMiddleware); From 3d27b88cb02a30a13bed4c6eac2f3e286a58208e Mon Sep 17 00:00:00 2001 From: erKam Date: Sat, 7 Nov 2026 17:53:17 +0000 Subject: [PATCH 04/32] feat(http): record the page's native fetch() calls fetch-collector.ts wraps window.fetch once and records each call next to the HttpClient calls with nativeFetch: true. It only looks: the app gets the original response and the body is never read. providePangularHttp() starts it at bootstrap, the overlay otherwise. withPangular() notes its requests in flight, so the fetch() that FetchBackend makes for them is not counted twice. The timeline tags these calls fetch() and list-http-calls flags them as native fetch. Refs #369 --- .../__tests__/network-native-fetch.test.ts | 48 +++++ app/src/pages/network-inspector.ts | 9 + apps/docs/src/content/agents/tools.md | 12 +- apps/docs/src/content/inspectors/ssr-http.md | 31 +-- packages/devtools/CHANGELOG.md | 2 +- .../src/__tests__/fetch-collector.test.ts | 196 ++++++++++++++++++ .../devtools/src/__tests__/http-tools.test.ts | 23 ++ .../src/__tests__/zone-collectors.test.ts | 19 ++ packages/devtools/src/devframe.ts | 2 +- packages/devtools/src/fetch-collector.ts | 158 ++++++++++++++ packages/devtools/src/http-record.ts | 38 ++++ packages/devtools/src/http-rules.ts | 11 + packages/devtools/src/http.ts | 37 ++-- packages/devtools/src/overlay.ts | 3 + packages/devtools/src/rpc/http-tools.ts | 1 + 15 files changed, 553 insertions(+), 37 deletions(-) create mode 100644 app/src/__tests__/network-native-fetch.test.ts create mode 100644 packages/devtools/src/__tests__/fetch-collector.test.ts create mode 100644 packages/devtools/src/fetch-collector.ts create mode 100644 packages/devtools/src/http-record.ts diff --git a/app/src/__tests__/network-native-fetch.test.ts b/app/src/__tests__/network-native-fetch.test.ts new file mode 100644 index 00000000..1ea74506 --- /dev/null +++ b/app/src/__tests__/network-native-fetch.test.ts @@ -0,0 +1,48 @@ +import { TestBed, type ComponentFixture } from '@angular/core/testing'; +import { afterEach, describe, expect, it } from 'vitest'; +import { NetworkInspector } from '../pages/network-inspector'; + +let fixture: ComponentFixture; + +const call = (id: string, overrides: Record = {}) => ({ + id, + url: `/api/${id}`, + method: 'GET', + status: 200, + durationMs: 12, + side: 'client' as const, + cacheHit: false, + faulted: false, + at: 1000, + ...overrides, +}); + +describe('NetworkInspector native fetch calls', () => { + afterEach(() => { + fixture?.destroy(); + TestBed.resetTestingModule(); + }); + + it('tags a native fetch() call and leaves HttpClient calls untagged', async () => { + fixture = TestBed.createComponent(NetworkInspector); + document.body.append(fixture.nativeElement); + fixture.componentInstance.pages.set([ + { + pageId: 'p1', + url: '/trips', + title: 'Trips', + hydration: null, + reportedAt: 1000, + calls: [call('client'), call('native', { nativeFetch: true })], + }, + ]); + await fixture.whenStable(); + const host = fixture.nativeElement as HTMLElement; + const rowOf = (id: string) => + host.querySelector(`button[data-call-id="${id}"]`)!.closest('tr')!; + const tags = (id: string) => + [...rowOf(id).querySelectorAll('.tag')].map((tag) => tag.textContent!.trim()); + expect(tags('native')).toContain('fetch()'); + expect(tags('client')).not.toContain('fetch()'); + }); +}); diff --git a/app/src/pages/network-inspector.ts b/app/src/pages/network-inspector.ts index 0cbb6cc8..6e8460fd 100644 --- a/app/src/pages/network-inspector.ts +++ b/app/src/pages/network-inspector.ts @@ -68,6 +68,8 @@ interface HttpCall { preview?: string; requestHeaders?: Record; responseHeaders?: Record; + /** Made with the page's own `fetch()`, not through `HttpClient`. */ + nativeFetch?: true; } interface SsrNavigation { @@ -689,6 +691,13 @@ const HTTP_STATUS_OPTIONS: SelectOption[] = [ {{ entry.durationMs }} ms
+ @if (entry.nativeFetch) { + fetch() + } @if (entry.cacheHit) { transfer cache } diff --git a/apps/docs/src/content/agents/tools.md b/apps/docs/src/content/agents/tools.md index c4d2fb14..789fb7dd 100644 --- a/apps/docs/src/content/agents/tools.md +++ b/apps/docs/src/content/agents/tools.md @@ -111,7 +111,7 @@ Gives one page's full picture in one bounded markdown answer, for an agent that | `components` | The component outline | `list-components` | | `route` | The URL and the active route tree | `inspect-route` | | `navigations` | Recent navigations, one line each, newest first | `explain-navigation` | -| `http` | HttpClient calls, newest first | `list-http-calls` | +| `http` | HttpClient and fetch() calls, newest first | `list-http-calls` | | `errors` | Error groups, most recent first | `list-errors` | | `console` | Console calls, newest first | `list-console` | | `forms` | Each form with its status and error count | `inspect-forms` | @@ -849,11 +849,11 @@ The **Live** line tells these cases apart: These tools read the HttpClient calls that `withPangular()` recorded and the SSR requests that `ssrMiddleware` or `withPangularSsr()` traced in your server. See [SSR & HTTP](../inspectors/ssr-http.md). Reads: page. -| Tool | What it answers | Arguments | -| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | -| `list-ssr-requests` | Recent traced SSR requests: id, URL, status, render mode, render time, server calls, and whether a connected page loaded the response. | `limit` | -| `explain-ssr-request` | One request end to end: timings, kept response headers, the router's guard and resolver times, each server call with its transfer cache outcome, then the browser page with its hydration result and the calls the browser made again. | `id`, `url` | -| `list-http-calls` | The HttpClient calls of one tab and the server render that served it, newest first: side (SSR or client), method, URL, status, duration, and flags for mocked, faulted, cancelled, redirected, transfer cache hit and the matched rule. With `preview`, the masked response body, for failed calls too. With `view: "waterfall"`, the tab's client calls as a waterfall instead. | `url`, `failed`, `limit`, `preview`, `headers`, `view` | +| Tool | What it answers | Arguments | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | +| `list-ssr-requests` | Recent traced SSR requests: id, URL, status, render mode, render time, server calls, and whether a connected page loaded the response. | `limit` | +| `explain-ssr-request` | One request end to end: timings, kept response headers, the router's guard and resolver times, each server call with its transfer cache outcome, then the browser page with its hydration result and the calls the browser made again. | `id`, `url` | +| `list-http-calls` | The HttpClient calls and native `fetch()` calls of one tab and the server render that served it, newest first: side (SSR or client), method, URL, status, duration, and flags for native fetch, mocked, faulted, cancelled, redirected, transfer cache hit and the matched rule. With `preview`, the masked response body, for failed calls too. With `view: "waterfall"`, the tab's client calls as a waterfall instead. | `url`, `failed`, `limit`, `preview`, `headers`, `view` | `explain-ssr-request` picks the request by `id`, else the newest one for `url`, else the newest one. diff --git a/apps/docs/src/content/inspectors/ssr-http.md b/apps/docs/src/content/inspectors/ssr-http.md index e629d855..a35fd187 100644 --- a/apps/docs/src/content/inspectors/ssr-http.md +++ b/apps/docs/src/content/inspectors/ssr-http.md @@ -118,8 +118,9 @@ The tab checks the TransferState after each server response, so **cached** is wh ### HTTP timeline -Every `HttpClient` request, tagged **SSR** or **Client**. Each row shows the method, the URL, the page that made it, the status, the time, and notes: +Every `HttpClient` request, tagged **SSR** or **Client**, and the page's own `fetch()` calls. Each row shows the method, the URL, the page that made it, the status, the time, and notes: +- **fetch()**: the page called `fetch()` itself, not through `HttpClient`, for example a library or a plain `await fetch(...)`. See [Native fetch calls](#native-fetch-calls). - **transfer cache**: the TransferState cache answered it. Only client requests get it, because the server never reads the cache; a server call shows its [transfer cache outcome](#transfer-cache-outcome) instead. A client request counts as a hit only when its method, response type, URL, body and params match the entry Angular stored, and Angular would cache it: no `transferCache: false`, auth headers, credentials or no-cache request. - **delayed N ms**: a fault rule held it back. - **mocked**: a fault rule answered it with a status below 400. @@ -142,6 +143,12 @@ If nothing matches, **Clear filters** resets all three. While the filters hide t Click a row for a response preview. The preview opens under the timeline and takes focus. A JSON body is indented by 2 spaces. Other text, and a body clipped at 2000 characters, shows as recorded. The preview is redacted before it reaches the panel, so masked values stay masked. Under the body, **Request headers** and **Response headers** list the call's headers. Request headers are the ones the call had when it reached `withPangular()`, so headers that later interceptors add are not in the list. Response headers are the ones the app could read; a cross-origin response shows only the headers CORS exposes. `Authorization`, `Proxy-Authorization`, `Cookie` and `Set-Cookie` are always masked, whatever `redaction.unmask` says. Names that look secret or match `redaction.secretNames`, and JWTs and bearer tokens in any value, are masked too. The interceptor keeps only a reference to the headers Angular already built. The overlay reads and masks them when it sends the call, so a page without the devtools copies nothing. **Copy** puts the shown preview on the clipboard. **Mock this request** fills in the [fault rule form](#fault-injection) from the call. **Close** or `Escape` returns focus to the row. A long timeline scrolls inside its own box. The timeline shows the page's client calls and the SSR calls made while rendering its first URL. **Clear timeline** empties it. +### Native fetch calls + +The overlay also records the page's own `fetch()` calls, marked **fetch()** in the timeline and `native fetch` in `list-http-calls`. It wraps `window.fetch` once and only looks: the app gets the original response, and the body is never read, so these calls have no response preview. Their headers are captured and masked like those of `HttpClient` calls. `providePangularHttp()` starts recording at bootstrap; without it, recording starts when the overlay connects, so calls made before then are missing. + +An `HttpClient` request on `withFetch()` that `withPangular()` records is listed once, as an `HttpClient` call: while it is in flight, a `fetch()` with the same method and URL is taken as Angular's own. A plain `fetch()` to that URL at the same moment is left out too. Calls to the devtools' own paths (`/__devframes/`) and Vite's module requests are never recorded. Fault rules and SSR overrides don't apply to native calls, and the server's own `fetch()` calls during a render are not recorded. + ### Fault injection Add a rule with these fields: @@ -205,17 +212,17 @@ Open an entry to see its cache key, even when the row shows the request URL. **C ### What each part needs -| Part | Needs | -| --------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| SSR requests | `ssrMiddleware` or `withPangularSsr()`, plus `withPangular()` for the server calls and the overlay for the linked page. | -| Router timings | `ssrMiddleware` and `providePangularHttp()`. | -| HTTP timeline | `withPangular()` and the overlay. | -| Fault injection | `withPangular()`. | -| Hydration stats | The overlay. | -| Hydration warnings | `providePangularHttp()`. | -| App stable after | `providePangularHttp()`. | -| SSR overrides | `ssrMiddleware` and `actions.http`. See [SSR overrides](#ssr-overrides) for each kind. | -| TransferState payload | The overlay, on a server-rendered page. | +| Part | Needs | +| --------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| SSR requests | `ssrMiddleware` or `withPangularSsr()`, plus `withPangular()` for the server calls and the overlay for the linked page. | +| Router timings | `ssrMiddleware` and `providePangularHttp()`. | +| HTTP timeline | `withPangular()` and the overlay. Native `fetch()` calls need the overlay only, or `providePangularHttp()` from bootstrap. | +| Fault injection | `withPangular()`. | +| Hydration stats | The overlay. | +| Hydration warnings | `providePangularHttp()`. | +| App stable after | `providePangularHttp()`. | +| SSR overrides | `ssrMiddleware` and `actions.http`. See [SSR overrides](#ssr-overrides) for each kind. | +| TransferState payload | The overlay, on a server-rendered page. | ### Development builds diff --git a/packages/devtools/CHANGELOG.md b/packages/devtools/CHANGELOG.md index 86c39dcd..dd6897b8 100644 --- a/packages/devtools/CHANGELOG.md +++ b/packages/devtools/CHANGELOG.md @@ -147,7 +147,7 @@ What remains: `/config` exports `DEFAULT_POPUP_HOTKEY`, `PANGULAR_ACTIONS`, `PAN - **Injectors:** a page picker, a created provider's value, startup timing and the slowest providers. - **NgRx Store:** full state for every entry still in the log, session export and import, generated tests, and NGXS, Elf and MiniRx through their Redux DevTools integration. - **TanStack Query:** a tab for queries and mutations, with invalidate and refetch. -- **SSR & HTTP:** a network waterfall, search and filters on the timeline, response preview format, copy and mock, TransferState keys and edits in SSR overrides, and SSR tracing for fetch handlers such as Hono, h3 and Nitro. +- **SSR & HTTP:** the page's own `fetch()` calls next to the `HttpClient` ones (#369), a network waterfall, search and filters on the timeline, response preview format, copy and mock, TransferState keys and edits in SSR overrides, and SSR tracing for fetch handlers such as Hono, h3 and Nitro. - **Errors:** a tab for `ErrorHandler` errors, uncaught errors, unhandled rejections and the console, with NG0xxx codes and explainers for NG0100, hydration mismatches and missing providers. - **Timeline:** one time axis with layers for the router, HTTP, NgRx, change detection, forms and signals. - **Audit:** one ranked list of findings across the inspectors, an axe pass, and before and after sessions. diff --git a/packages/devtools/src/__tests__/fetch-collector.test.ts b/packages/devtools/src/__tests__/fetch-collector.test.ts new file mode 100644 index 00000000..346cbba5 --- /dev/null +++ b/packages/devtools/src/__tests__/fetch-collector.test.ts @@ -0,0 +1,196 @@ +// @vitest-environment jsdom +import '@angular/compiler'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { TestBed } from '@angular/core/testing'; +import { HttpClient, provideHttpClient, withFetch } from '@angular/common/http'; +import { firstValueFrom } from 'rxjs'; +import { requestOf, watchNativeFetch } from '../fetch-collector.ts'; +import { notePendingClientRequest } from '../http-record.ts'; +import { httpRegistry, sanitizeCalls } from '../http-rules.ts'; +import { readHeaders, withCapturedHeaders } from '../http-headers.ts'; +import { withPangular } from '../http.ts'; +import { angularAtLeast, initAngularTestBed, zonelessProviders } from './angular-version.ts'; + +const g = globalThis as { fetch?: typeof fetch; __PANGULAR_HTTP__?: unknown }; +let realFetch: typeof fetch | undefined; +let stop: () => void = () => {}; + +function stubFetch(answer: (input: unknown, init?: RequestInit) => Promise) { + const stub = vi.fn(answer); + g.fetch = stub as unknown as typeof fetch; + return stub; +} + +const flush = () => new Promise((resolve) => setTimeout(resolve, 0)); +const calls = () => httpRegistry().calls ?? []; + +beforeEach(() => { + realFetch = g.fetch; + delete g.__PANGULAR_HTTP__; +}); + +afterEach(() => { + stop(); + stop = () => {}; + g.fetch = realFetch; + delete g.__PANGULAR_HTTP__; +}); + +describe('watchNativeFetch', () => { + it('records a fetch() call and hands the app the original response', async () => { + const response = new Response('{"ok":true}', { + status: 201, + headers: { 'content-type': 'application/json' }, + }); + const stub = stubFetch(async () => response); + stop = watchNativeFetch({ + now: (() => { + let t = 1000; + return () => (t += 25); + })(), + }); + const got = await fetch('/api/trips', { + method: 'post', + headers: { 'x-trace': 'abc' }, + body: '{}', + }); + await flush(); + expect(got).toBe(response); + expect(stub).toHaveBeenCalledTimes(1); + expect(calls()).toHaveLength(1); + const call = calls()[0]!; + expect(call).toMatchObject({ + url: '/api/trips', + method: 'POST', + status: 201, + side: 'client', + nativeFetch: true, + durationMs: 25, + }); + expect(call.error).toBeUndefined(); + const read = withCapturedHeaders(call); + expect(read.requestHeaders).toEqual({ 'x-trace': 'abc' }); + expect(read.responseHeaders).toEqual({ 'content-type': 'application/json' }); + // The body is left for the app. + expect(await got.json()).toEqual({ ok: true }); + }); + + it('notes an error status, a network failure and an abort', async () => { + let mode: 'status' | 'network' | 'abort' = 'status'; + stubFetch(async () => { + if (mode === 'network') throw new TypeError('Failed to fetch'); + if (mode === 'abort') throw new DOMException('The user aborted a request.', 'AbortError'); + return new Response('nope', { status: 404, statusText: 'Not Found' }); + }); + stop = watchNativeFetch(); + await fetch('/api/missing'); + mode = 'network'; + await expect(fetch('/api/down')).rejects.toThrow('Failed to fetch'); + mode = 'abort'; + await expect(fetch('/api/slow')).rejects.toThrow(); + await flush(); + expect(calls().map((c) => [c.url, c.status, c.error, c.cancelled ?? false])).toEqual([ + ['/api/missing', 404, '404 Not Found', false], + ['/api/down', 0, 'Failed to fetch', false], + ['/api/slow', 0, 'cancelled', true], + ]); + }); + + it("skips the devtools' own requests and URLs the caller skips", async () => { + stubFetch(async () => new Response('')); + stop = watchNativeFetch({ skip: (url) => url.includes('/metrics') }); + await fetch('/__devframes/pangular/__connection.json'); + await fetch('http://localhost:4000/__devframes/__rpc'); + await fetch('/metrics'); + await fetch('/api/kept'); + await flush(); + expect(calls().map((c) => c.url)).toEqual(['/api/kept']); + }); + + it('skips the fetch() of an HttpClient request withPangular() records', async () => { + stubFetch(async () => new Response('')); + stop = watchNativeFetch(); + const end = notePendingClientRequest('GET', '/api/products'); + await fetch('/api/products'); + end(); + await fetch('/api/products'); + await flush(); + expect(calls()).toHaveLength(1); + expect(httpRegistry().clientPending?.size).toBe(0); + }); + + it('wraps fetch once and restores it', async () => { + const stub = stubFetch(async () => new Response('')); + stop = watchNativeFetch(); + const wrapped = g.fetch; + expect(wrapped).not.toBe(stub); + const second = watchNativeFetch(); + expect(g.fetch).toBe(wrapped); + second(); + expect(g.fetch).toBe(wrapped); + stop(); + expect(g.fetch).toBe(stub); + }); + + it('keeps the calls the page keeps, and the sanitizer keeps the flag', async () => { + stubFetch(async () => new Response('')); + httpRegistry().maxCalls = 2; + stop = watchNativeFetch(); + await fetch('/a'); + await fetch('/b'); + await fetch('/c'); + await flush(); + expect(calls().map((c) => c.url)).toEqual(['/b', '/c']); + expect(httpRegistry().dropped).toBe(1); + expect(sanitizeCalls(calls())[0]!.nativeFetch).toBe(true); + expect(sanitizeCalls([{ ...calls()[0]!, nativeFetch: 'yes' }])[0]!.nativeFetch).toBeUndefined(); + }); + + it('reads the URL, method and headers of a Request', () => { + const request = new Request('http://localhost/api/x', { + method: 'PUT', + headers: { 'x-a': '1' }, + }); + const read = requestOf(request, undefined); + expect(read.url).toBe('http://localhost/api/x'); + expect(read.method).toBe('PUT'); + expect( + readHeaders({ + keys: () => [...new Headers(read.headers).keys()], + getAll: (n) => [new Headers(read.headers).get(n)!], + }), + ).toEqual({ 'x-a': '1' }); + expect(requestOf(new URL('http://localhost/b'), { method: 'delete' })).toMatchObject({ + url: 'http://localhost/b', + method: 'DELETE', + }); + }); +}); + +describe.runIf(angularAtLeast('19.0'))('native fetch with HttpClient', () => { + afterEach(() => TestBed.resetTestingModule()); + + it('records an HttpClient request on withFetch() once, as an HttpClient call', async () => { + (globalThis as { ngDevMode?: unknown }).ngDevMode = true; + const stub = stubFetch( + async () => + new Response('[1,2]', { status: 200, headers: { 'content-type': 'application/json' } }), + ); + stop = watchNativeFetch(); + initAngularTestBed(); + TestBed.configureTestingModule({ + providers: [...zonelessProviders(), provideHttpClient(withFetch(), withPangular())], + }); + const http = TestBed.inject(HttpClient); + expect(await firstValueFrom(http.get('/api/list'))).toEqual([1, 2]); + await flush(); + expect(stub).toHaveBeenCalledTimes(1); + expect(calls()).toHaveLength(1); + expect(calls()[0]!.nativeFetch).toBeUndefined(); + expect(httpRegistry().clientPending?.size).toBe(0); + // A plain fetch() to the same URL afterwards is a native call. + await fetch('/api/list'); + await flush(); + expect(calls().map((c) => c.nativeFetch ?? false)).toEqual([false, true]); + }); +}); diff --git a/packages/devtools/src/__tests__/http-tools.test.ts b/packages/devtools/src/__tests__/http-tools.test.ts index a0ffd275..de92c0e1 100644 --- a/packages/devtools/src/__tests__/http-tools.test.ts +++ b/packages/devtools/src/__tests__/http-tools.test.ts @@ -262,3 +262,26 @@ describe('redirected calls', () => { expect(text).toMatch(/redirected to `\/login\?token=[^`]*`/); }); }); + +describe('list-http-calls with native fetch calls', () => { + it('flags a native fetch() call and not an HttpClient call', () => { + const text = listHttpCallsText( + { + serverCalls: [], + pages: [ + page({ + calls: [ + call({ id: 'n1', url: '/api/native', nativeFetch: true }), + call({ id: 'h1', url: '/api/client' }), + ], + }), + ], + }, + { page: 'p1' }, + NOW, + ); + const line = (url: string) => text.split('\n').find((l) => l.includes(url)) ?? ''; + expect(line('/api/native')).toContain('native fetch'); + expect(line('/api/client')).not.toContain('native fetch'); + }); +}); diff --git a/packages/devtools/src/__tests__/zone-collectors.test.ts b/packages/devtools/src/__tests__/zone-collectors.test.ts index e0c78172..e50d19a9 100644 --- a/packages/devtools/src/__tests__/zone-collectors.test.ts +++ b/packages/devtools/src/__tests__/zone-collectors.test.ts @@ -10,6 +10,8 @@ import { QueryClient } from '@tanstack/query-core'; import { afterEach, describe, expect, it } from 'vitest'; import { ANALOG_META_DESCRIPTION, attachAnalog } from '../analog-runtime.ts'; import { attachErrors } from '../errors-collector.ts'; +import { watchNativeFetch } from '../fetch-collector.ts'; +import { httpRegistry } from '../http-rules.ts'; import { attachForms } from '../forms-collector.ts'; import { attachNgrx } from '../ngrx-overlay.ts'; import { @@ -142,6 +144,23 @@ describe('collectors under zone.js', () => { expect(result.withCollector.stable).toBe(true); }); + it('native fetch: a fetch() from app code adds no tick and keeps the zone stable', async () => { + const g = globalThis as { fetch?: typeof fetch; __PANGULAR_HTTP__?: unknown }; + const original = g.fetch; + g.fetch = (async () => new Response('ok')) as typeof fetch; + stops.push(() => { + g.fetch = original; + delete g.__PANGULAR_HTTP__; + }); + const result = await compare( + () => watchNativeFetch(), + () => void fetch('/api/trips').then((response) => response.text()), + ); + expect(httpRegistry().calls?.some((call) => call.nativeFetch)).toBe(true); + expect(result.withCollector).toEqual(result.without); + expect(result.withCollector.stable).toBe(true); + }); + it('redux-style stores: a store change adds no tick', async () => { const target: Record = {}; installReduxDevtoolsHook(target); diff --git a/packages/devtools/src/devframe.ts b/packages/devtools/src/devframe.ts index b42bb213..415a284b 100644 --- a/packages/devtools/src/devframe.ts +++ b/packages/devtools/src/devframe.ts @@ -1549,7 +1549,7 @@ const pangular = defineDevframe({ id: 'pangular:list-http-calls', title: 'List HTTP calls', description: - 'HttpClient calls recorded by withPangular(), newest first: side, method, URL, status, duration, rule and cache flags, and the response preview. `view: "waterfall"` draws a page\'s client calls per navigation instead. `headers` adds headers. Masked. Empty without the interceptor.', + 'HttpClient calls recorded by withPangular() and the page\'s own fetch() calls (flag `native fetch`), newest first: side, method, URL, status, duration, rule and cache flags, and the response preview. `view: "waterfall"` draws a page\'s client calls per navigation instead. `headers` adds headers. Masked. Empty without the interceptor or a page.', inputSchema: { type: 'object', properties: { diff --git a/packages/devtools/src/fetch-collector.ts b/packages/devtools/src/fetch-collector.ts new file mode 100644 index 00000000..c7f4c496 --- /dev/null +++ b/packages/devtools/src/fetch-collector.ts @@ -0,0 +1,158 @@ +// Records the page's own `fetch()` calls (a library, `httpResource` with a +// custom loader, a plain `await fetch(...)`) next to the HttpClient calls +// `withPangular()` records. It wraps `globalThis.fetch` once and only looks: +// the request, the response and the promise the app gets back are the +// originals. Response bodies are never read, so nothing is consumed. + +import { outsideAngular } from './outside-angular.ts'; +import { captureHeaders, type HeaderSource } from './http-headers.ts'; +import { pendingKey, recordHttpCall } from './http-record.ts'; +import { httpRegistry, type HttpCall } from './http-rules.ts'; + +/** URLs of the devtools' own requests, which are never recorded. */ +const OWN_REQUEST = /\/__devframes\/|\/__pangular|\/@vite\/|\/@fs\/|\/@id\//; + +export interface NativeFetchOptions { + /** Skips a call when this returns true for its URL, after the devtools' own paths. */ + skip?: (url: string) => boolean; + /** The clock, for tests. */ + now?: () => number; +} + +let seq = 0; + +/** The URL and method of a `fetch()` call, without reading a `Request` body. */ +export function requestOf( + input: unknown, + init: RequestInit | undefined, +): { url: string; method: string; headers?: HeadersInit } { + let url = ''; + let method = 'GET'; + let headers: HeadersInit | undefined; + if (typeof input === 'string') url = input; + else if (input instanceof URL) url = input.href; + else if (input && typeof input === 'object' && 'url' in input) { + const request = input as Request; + url = String(request.url); + method = request.method || method; + headers = request.headers; + } + if (init?.method) method = init.method; + if (init?.headers) headers = init.headers; + return { url, method: method.toUpperCase(), headers }; +} + +/** `Headers` as the `HttpHeaders` shape the header capture reads. */ +function headerSource(init: HeadersInit | Headers | undefined): HeaderSource | undefined { + if (!init) return undefined; + try { + const headers = init instanceof Headers ? init : new Headers(init); + return { + keys: () => [...headers.keys()], + getAll: (name) => { + const value = headers.get(name); + return value === null ? null : [value]; + }, + }; + } catch { + return undefined; + } +} + +/** True for an `AbortController.abort()`, which the app asked for. */ +function isAbort(error: unknown): boolean { + return ( + !!error && typeof error === 'object' && (error as { name?: unknown }).name === 'AbortError' + ); +} + +/** Whether this `fetch()` belongs to an `HttpClient` request `withPangular()` records. */ +function isClientRequest(method: string, url: string): boolean { + const pending = httpRegistry().clientPending; + if (!pending?.size) return false; + const key = pendingKey(method, url); + return (pending.get(key) ?? 0) > 0; +} + +/** + * Wraps `globalThis.fetch` so each call is recorded as an HTTP call with + * `nativeFetch: true`. Calls to the devtools' own paths and the `fetch()` + * that Angular's `FetchBackend` makes for an `HttpClient` request + * `withPangular()` already records are skipped. Safe to call more than once; + * the returned function restores the original `fetch`. + */ +export function watchNativeFetch(options: NativeFetchOptions = {}): () => void { + const g = globalThis as { fetch?: typeof fetch }; + const registry = httpRegistry(); + const original = g.fetch; + if (typeof original !== 'function') return () => {}; + if (registry.nativeFetch) return () => {}; + registry.nativeFetch = original; + const now = options.now ?? Date.now; + + const watched = function (this: unknown, input: RequestInfo | URL, init?: RequestInit) { + const result = original.call(this ?? globalThis, input, init); + let request: ReturnType; + try { + request = requestOf(input, init); + } catch { + return result; + } + const { url, method } = request; + if (!url || OWN_REQUEST.test(url) || options.skip?.(url) || isClientRequest(method, url)) { + return result; + } + const started = now(); + const pageUrl = + typeof location === 'undefined' ? undefined : location.pathname + location.search; + const done = (fields: Partial, response?: Response) => { + const call: HttpCall = { + id: `f${started.toString(36)}${++seq}`, + url, + method, + status: 0, + durationMs: Math.max(0, now() - started), + side: 'client', + cacheHit: false, + faulted: false, + at: started, + nativeFetch: true, + ...(pageUrl === undefined ? {} : { pageUrl }), + ...fields, + }; + // Only references: the headers are read and masked when the call is reported. + captureHeaders(call, headerSource(request.headers), headerSource(response?.headers)); + recordHttpCall(call); + }; + // The callbacks run outside the Angular zone, so recording starts no extra tick. + outsideAngular(() => + result.then( + (response) => { + const redirected = response.redirected && response.url && response.url !== url; + done( + { + status: response.status, + ...(redirected ? { redirectedTo: response.url } : {}), + ...(response.ok ? {} : { error: `${response.status} ${response.statusText}`.trim() }), + }, + response, + ); + }, + (error: unknown) => { + done( + isAbort(error) + ? { cancelled: true, error: 'cancelled' } + : { error: String((error as Error)?.message ?? error).slice(0, 500) }, + ); + }, + ), + ); + return result; + } as typeof fetch; + + g.fetch = watched; + return () => { + if (g.fetch === watched) g.fetch = original; + if (registry.nativeFetch === original) delete registry.nativeFetch; + }; +} diff --git a/packages/devtools/src/http-record.ts b/packages/devtools/src/http-record.ts new file mode 100644 index 00000000..484a0e8e --- /dev/null +++ b/packages/devtools/src/http-record.ts @@ -0,0 +1,38 @@ +// Stores one recorded HTTP call: on the server the devframe takes it right +// away (`registry.record`), in the browser it joins the page's capped list. + +import { withCapturedHeaders } from './http-headers.ts'; +import { MAX_CALLS, httpRegistry, type HttpCall } from './http-rules.ts'; + +export function recordHttpCall(call: HttpCall): void { + const registry = httpRegistry(); + // The devframe server takes the call right away, so its headers are read now. + if (registry.record) return registry.record(withCapturedHeaders(call)); + const calls = (registry.calls ??= []); + calls.push(call); + const extra = calls.length - (registry.maxCalls ?? MAX_CALLS); + if (extra > 0) { + calls.splice(0, extra); + registry.dropped = (registry.dropped ?? 0) + extra; + } +} + +/** The key `clientPending` counts a request under. */ +export function pendingKey(method: string, url: string): string { + return `${method.toUpperCase()} ${url}`; +} + +/** Notes an `HttpClient` request in flight; the returned function ends it once. */ +export function notePendingClientRequest(method: string, url: string): () => void { + const pending = (httpRegistry().clientPending ??= new Map()); + const key = pendingKey(method, url); + pending.set(key, (pending.get(key) ?? 0) + 1); + let done = false; + return () => { + if (done) return; + done = true; + const left = (pending.get(key) ?? 1) - 1; + if (left > 0) pending.set(key, left); + else pending.delete(key); + }; +} diff --git a/packages/devtools/src/http-rules.ts b/packages/devtools/src/http-rules.ts index 46d70a14..8b73136a 100644 --- a/packages/devtools/src/http-rules.ts +++ b/packages/devtools/src/http-rules.ts @@ -52,6 +52,8 @@ export interface HttpCall { requestHeaders?: HeaderRecord; /** The response headers the app could read, redacted before they leave the page. */ responseHeaders?: HeaderRecord; + /** Made with the page's `fetch()` directly, not through `HttpClient` (`fetch-collector.ts`). */ + nativeFetch?: true; } export interface HttpRegistry { @@ -72,6 +74,14 @@ export interface HttpRegistry { rulesOff?: boolean; /** Browser only: navigation start until the app first became stable. */ stableMs?: number; + /** + * Browser only: `METHOD url` of the `HttpClient` requests `withPangular()` + * is recording, so the native fetch watcher skips the `fetch()` that + * Angular's `FetchBackend` makes for them. + */ + clientPending?: Map; + /** Browser only: the page's `fetch` before the native fetch watcher wrapped it. */ + nativeFetch?: typeof fetch; } export const MAX_CALLS = 200; @@ -197,6 +207,7 @@ export function sanitizeCalls(input: unknown, max = MAX_CALLS): HttpCall[] { error: str(c.error, 500), preview: str(c.preview, 2001), ...headerFields(c.requestHeaders, c.responseHeaders), + ...(c.nativeFetch === true ? { nativeFetch: true as const } : {}), }); } return calls; diff --git a/packages/devtools/src/http.ts b/packages/devtools/src/http.ts index 851bdd9e..e9fac60f 100644 --- a/packages/devtools/src/http.ts +++ b/packages/devtools/src/http.ts @@ -27,10 +27,11 @@ import { redactMessage } from './forms-privacy.ts'; import { transferCacheKeys } from './http-cache-key.ts'; import { cacheSkipReason, requestSkipReason, type CacheSkip } from './http-cache-reason.ts'; import { isHydrationMessage } from './http-hydration.ts'; -import { captureHeaders, withCapturedHeaders, type HeaderSource } from './http-headers.ts'; +import { captureHeaders, type HeaderSource } from './http-headers.ts'; +import { notePendingClientRequest, recordHttpCall } from './http-record.ts'; +import { watchNativeFetch } from './fetch-collector.ts'; import { - MAX_CALLS, MAX_DELAY_MS, clientRules, httpRegistry, @@ -87,19 +88,6 @@ function optionalPreview(body: unknown): { preview?: string } { return text === undefined ? {} : { preview: text }; } -function record(call: HttpCall) { - const registry = httpRegistry(); - // The devframe server takes the call right away, so its headers are read now. - if (registry.record) return registry.record(withCapturedHeaders(call)); - const calls = (registry.calls ??= []); - calls.push(call); - const extra = calls.length - (registry.maxCalls ?? MAX_CALLS); - if (extra > 0) { - calls.splice(0, extra); - registry.dropped = (registry.dropped ?? 0) + extra; - } -} - let seq = 0; const stableApps = new WeakSet(); @@ -196,7 +184,7 @@ export const pangularHttpInterceptor: HttpInterceptorFn = (req, next) => { }; // Only references: the headers are read when the call is reported. captureHeaders(call, req.headers, responseHeaders); - record(call); + recordHttpCall(call); }; let source: Observable>; @@ -245,10 +233,14 @@ export const pangularHttpInterceptor: HttpInterceptorFn = (req, next) => { // arrives before subscribe() returns came from the SSR payload. let sync = true; let settled = false; + // The `fetch()` Angular's FetchBackend makes for this request is not a native call. + const endPending = + side === 'client' && !mocked ? notePendingClientRequest(req.method, url) : () => {}; const inner = source.subscribe({ next: (event) => { if (event instanceof HttpResponse) { settled = true; + endPending(); const fromPayload = claimable && claimTransferEntry(req); done( { @@ -268,6 +260,7 @@ export const pangularHttpInterceptor: HttpInterceptorFn = (req, next) => { const failed = error instanceof HttpErrorResponse; const message = failed ? error.message : String(error); settled = true; + endPending(); done( { status: failed ? error.status : 0, @@ -286,6 +279,7 @@ export const pangularHttpInterceptor: HttpInterceptorFn = (req, next) => { }); sync = false; return () => { + endPending(); if (!settled) { settled = true; done({ status: 0, cacheHit: false, cancelled: true, error: 'cancelled' }); @@ -371,7 +365,8 @@ function watchServerNavigations() { } /** - * Captures hydration warnings (NG05xx) before the overlay loads. During a + * Captures hydration warnings (NG05xx) and the page's native `fetch()` calls + * before the overlay loads. During a * server render traced by `ssrMiddleware`, it also records the router's guard * and resolver timings and applies render-error overrides from the panel. * Add it next to `provideHttpClient(withPangular())`. @@ -381,9 +376,17 @@ export function providePangularHttp(): EnvironmentProviders { provideEnvironmentInitializer(captureHydrationWarnings), provideEnvironmentInitializer(watchServerNavigations), provideEnvironmentInitializer(timeUntilStable), + provideEnvironmentInitializer(watchNativeFetchEarly), ]); } +/** Records the page's own `fetch()` calls from bootstrap on, before the overlay connects. */ +function watchNativeFetchEarly() { + const platform = inject(PLATFORM_ID, { optional: true }); + if (!devMode() || !platform || isPlatformServer(platform)) return; + watchNativeFetch(); +} + /** Milliseconds from navigation start until the app first became stable, the point hydration finishes. */ function timeUntilStable() { const platform = inject(PLATFORM_ID, { optional: true }); diff --git a/packages/devtools/src/overlay.ts b/packages/devtools/src/overlay.ts index 8a57dc98..a7f896cf 100644 --- a/packages/devtools/src/overlay.ts +++ b/packages/devtools/src/overlay.ts @@ -13,6 +13,7 @@ import { attachAnalog } from './analog-runtime.ts'; import { attachForms } from './forms-collector.ts'; import { attachPipes } from './pipes-collector.ts'; import { attachHttp } from './http-overlay.ts'; +import { watchNativeFetch } from './fetch-collector.ts'; import { attachCapacitor } from './capacitor-overlay.ts'; import { attachErrors } from './errors-collector.ts'; import { attachLeaks } from './leaks-collector.ts'; @@ -554,6 +555,8 @@ async function startOverlay( const pipes = on.pipes ? attachPipes(my, pageId, getNg) : null; const http = on.http ? attachHttp(my, pageId, tickMs) : null; if (http) own(http.stop); + // The page's own fetch() calls, next to the HttpClient ones (no-op when providePangularHttp did it). + if (http) own(watchNativeFetch()); const capacitor = on.capacitor ? attachCapacitor(my, pageId, { maxCalls: limits.capacitorCalls, diff --git a/packages/devtools/src/rpc/http-tools.ts b/packages/devtools/src/rpc/http-tools.ts index 486aaa92..da415318 100644 --- a/packages/devtools/src/rpc/http-tools.ts +++ b/packages/devtools/src/rpc/http-tools.ts @@ -39,6 +39,7 @@ export function isFailedCall(call: HttpCall): boolean { function flags(call: HttpCall): string { const out: string[] = []; + if (call.nativeFetch) out.push('native fetch'); if (call.mocked) out.push('mocked'); if (call.faulted) out.push('faulted'); if (call.cancelled) out.push('cancelled'); From 2c9b134a298a2617738d5c05926c85647364247c Mon Sep 17 00:00:00 2001 From: erKam Date: Sat, 7 Nov 2026 18:20:32 +0000 Subject: [PATCH 05/32] feat: capture SSR and Vite errors on the dev server server-errors.ts records what no page can report: console.error during a traced server render or in Angular's ErrorHandler format, and the error payloads Vite sends to its browser overlay (Vite plugin only). The devframe server keeps them in the server-errors shared state, grouped and masked, expires a group 30 minutes after its last repeat, and serves them through list-server-errors and a Server errors section on the Errors tab. Also shortens the list-http-calls description back under the 300 character limit the tool description test sets. Refs #372 --- .../__tests__/server-errors-section.test.ts | 114 +++++++ app/src/pages/errors-inspector.ts | 4 +- app/src/pages/server-errors-section.ts | 293 ++++++++++++++++++ apps/docs/src/content/agents/tools.md | 15 +- apps/docs/src/content/inspectors/errors.md | 22 +- packages/devtools/CHANGELOG.md | 4 +- .../src/__tests__/docs-tool-count.test.ts | 23 +- .../fixtures/agent-metadata-derived.json | 3 + .../src/__tests__/server-errors.test.ts | 245 +++++++++++++++ .../src/__tests__/vite-errors.test.ts | 43 +++ packages/devtools/src/config.ts | 4 + packages/devtools/src/devframe.ts | 9 +- .../src/rpc/server-errors-register.ts | 284 +++++++++++++++++ packages/devtools/src/server-errors.ts | 197 ++++++++++++ packages/devtools/src/vite.ts | 9 +- 15 files changed, 1251 insertions(+), 18 deletions(-) create mode 100644 app/src/__tests__/server-errors-section.test.ts create mode 100644 app/src/pages/server-errors-section.ts create mode 100644 packages/devtools/src/__tests__/server-errors.test.ts create mode 100644 packages/devtools/src/__tests__/vite-errors.test.ts create mode 100644 packages/devtools/src/rpc/server-errors-register.ts create mode 100644 packages/devtools/src/server-errors.ts diff --git a/app/src/__tests__/server-errors-section.test.ts b/app/src/__tests__/server-errors-section.test.ts new file mode 100644 index 00000000..52c1245e --- /dev/null +++ b/app/src/__tests__/server-errors-section.test.ts @@ -0,0 +1,114 @@ +import { TestBed, type ComponentFixture } from '@angular/core/testing'; +import type { DevframeRpcClient } from 'devframe/client'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { + ServerErrorsSection, + serverErrorPlace, + serverErrorsOf, + type ServerErrorGroup, +} from '../pages/server-errors-section'; + +const group = (extra: Partial = {}): ServerErrorGroup => ({ + id: 's1', + kind: 'ssr', + message: 'NG0201: No provider found for `TripsApi`', + count: 2, + firstAt: 1_000, + lastAt: 2_000, + url: '/trips', + stack: 'Error: NG0201\n at render', + ...extra, +}); + +function fakeClient(value: unknown) { + let listener: ((value: unknown) => void) | undefined; + const call = vi.fn(() => Promise.resolve()); + const scoped = { + rpc: { + call, + sharedState: (name: string) => + Promise.resolve({ + value: () => (name === 'server-errors' ? value : null), + on: (_event: string, fn: (value: unknown) => void) => { + listener = fn; + return () => {}; + }, + }), + }, + }; + const client = { scope: () => scoped } as unknown as DevframeRpcClient; + return { client, call, push: (next: unknown) => listener?.(next) }; +} + +let fixture: ComponentFixture; + +async function render(value: unknown) { + const fake = fakeClient(value); + fixture = TestBed.createComponent(ServerErrorsSection); + fixture.componentRef.setInput('rpc', fake.client); + document.body.append(fixture.nativeElement); + await fixture.whenStable(); + await new Promise((resolve) => setTimeout(resolve, 0)); + await fixture.whenStable(); + return { host: fixture.nativeElement as HTMLElement, ...fake }; +} + +describe('server errors section', () => { + afterEach(() => { + fixture?.destroy(); + TestBed.resetTestingModule(); + }); + + it('shows nothing while the server has no errors', async () => { + const { host } = await render({ errors: [], dropped: 0 }); + expect(host.querySelector('section')).toBeNull(); + }); + + it('lists SSR and Vite errors with where they happened, and clears them', async () => { + const { host, call, push } = await render({ + errors: [ + group(), + group({ + id: 's2', + kind: 'vite', + message: 'Transform failed', + count: 1, + url: undefined, + file: '/src/app.ts', + line: 3, + column: 7, + plugin: 'vite:esbuild', + frame: '3 | let = 1', + }), + ], + dropped: 0, + }); + const items = [...host.querySelectorAll('li.item')]; + expect(items).toHaveLength(2); + expect(items[0]!.textContent).toContain('SSR'); + expect(items[0]!.textContent).toContain('2×'); + expect(items[0]!.textContent).toContain('Render of /trips'); + expect(items[1]!.textContent).toContain('/src/app.ts:3:7'); + expect(items[1]!.querySelector('details summary')!.textContent).toBe('Code frame'); + const clear = [...host.querySelectorAll('button')].find((b) => + b.textContent!.includes('Clear server errors'), + )!; + clear.click(); + await fixture.whenStable(); + expect(call).toHaveBeenCalledWith('clear-server-errors'); + push({ errors: [], dropped: 0 }); + await fixture.whenStable(); + expect(host.querySelector('section')).toBeNull(); + }); + + it('drops malformed groups and words the place of each kind', () => { + expect( + serverErrorsOf({ errors: [group(), { id: 3 }, null, { ...group(), kind: 'x' }] }), + ).toHaveLength(1); + expect(serverErrorsOf(null)).toEqual([]); + expect(serverErrorPlace(group({ url: undefined }))).toBe('Server render'); + expect(serverErrorPlace(group({ kind: 'vite', file: undefined, plugin: 'vite:css' }))).toBe( + 'vite:css', + ); + }); +}); diff --git a/app/src/pages/errors-inspector.ts b/app/src/pages/errors-inspector.ts index 28563846..414c0e10 100644 --- a/app/src/pages/errors-inspector.ts +++ b/app/src/pages/errors-inspector.ts @@ -15,6 +15,7 @@ import { time } from '../format'; import { hostPageId } from '../page-id'; import { Select, type SelectOption } from '../ui/select'; import { ErrorExplainView } from './error-explain'; +import { ServerErrorsSection } from './server-errors-section'; import type { ComponentFocus } from '../types/component-focus.types'; import { ALL_PAGES, @@ -41,8 +42,9 @@ const LEVEL_TONE: Record = { @Component({ selector: 'app-errors-inspector', - imports: [Select, ErrorExplainView], + imports: [Select, ErrorExplainView, ServerErrorsSection], template: ` + @if (!loaded()) {

Loading errors…

} @else if (failed()) { diff --git a/app/src/pages/server-errors-section.ts b/app/src/pages/server-errors-section.ts new file mode 100644 index 00000000..7d655293 --- /dev/null +++ b/app/src/pages/server-errors-section.ts @@ -0,0 +1,293 @@ +import { Component, DestroyRef, effect, inject, input, signal } from '@angular/core'; +import type { DevframeRpcClient } from 'devframe/client'; +import { time } from '../format'; + +/** One group of dev server errors, as `rpc/server-errors-register.ts` keeps it. */ +export interface ServerErrorGroup { + id: string; + kind: 'ssr' | 'vite'; + message: string; + count: number; + firstAt: number; + lastAt: number; + code?: string; + stack?: string; + url?: string; + requestId?: string; + plugin?: string; + file?: string; + line?: number; + column?: number; + frame?: string; +} + +/** The groups of the `server-errors` shared state, most recent first; bad entries are dropped. */ +export function serverErrorsOf(value: unknown): ServerErrorGroup[] { + const errors = (value as { errors?: unknown } | null)?.errors; + if (!Array.isArray(errors)) return []; + return errors.filter( + (e): e is ServerErrorGroup => + !!e && + typeof e === 'object' && + typeof (e as ServerErrorGroup).id === 'string' && + typeof (e as ServerErrorGroup).message === 'string' && + ((e as ServerErrorGroup).kind === 'ssr' || (e as ServerErrorGroup).kind === 'vite'), + ); +} + +/** Where a group happened: the render's page, or the file and line Vite named. */ +export function serverErrorPlace(group: ServerErrorGroup): string { + if (group.kind === 'ssr') return group.url ? `Render of ${group.url}` : 'Server render'; + if (!group.file) return group.plugin ?? ''; + const line = group.line !== undefined ? `:${group.line}` : ''; + const column = group.line !== undefined && group.column !== undefined ? `:${group.column}` : ''; + return `${group.file}${line}${column}`; +} + +/** + * Errors the dev server hit that no page can report: a server render that + * failed and Vite's transform and compile errors. Shows nothing while there + * are none, so the Errors tab looks as before. + */ +@Component({ + selector: 'app-server-errors-section', + template: ` + @if (errors().length) { +
+
+

+ Server errors {{ errors().length }} +

+ +
+

+ From the dev server: SSR renders that logged an error, and Vite transform and compile + errors. Masked; each group expires 30 minutes after its last repeat. +

+
    + @for (group of errors(); track group.id) { +
  1. +
    + {{ + group.kind === 'ssr' ? 'SSR' : 'Vite' + }} + @if (group.count > 1) { + {{ group.count }}× + } + {{ group.message }} +
    +
    + @if (place(group); as where) { + {{ where }} + } + @if (group.plugin && group.file) { + {{ group.plugin }} + } + @if (group.code) { + {{ group.code }} + } + Last at {{ time(group.lastAt) }} +
    + @if (group.frame) { +
    + Code frame +
    {{ group.frame }}
    +
    + } + @if (group.stack) { +
    + Stack +
    {{ group.stack }}
    +
    + } +
  2. + } +
+

{{ status() }}

+
+ } + `, + styles: ` + @use 'mixins' as m; + :host { + display: block; + } + .server { + margin: 0 0 16px; + } + .bar { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + justify-content: space-between; + } + h2 { + display: inline-flex; + gap: 6px; + align-items: center; + margin: 0; + color: var(--text-strong); + font-size: 14px; + } + .badge { + padding: 0 6px; + border-radius: 99px; + background: var(--surface-2); + color: var(--text-2); + font-size: 11px; + } + .muted { + color: var(--text-2); + } + p.muted { + margin: 6px 0 8px; + line-height: 1.5; + } + code, + pre { + font-family: var(--font-mono); + font-size: 12px; + } + .list { + display: grid; + gap: 6px; + margin: 0; + padding: 0; + list-style: none; + } + .item { + @include m.panel; + display: grid; + gap: 6px; + min-width: 0; + padding: 8px 12px; + } + .head { + display: flex; + gap: 8px; + align-items: baseline; + min-width: 0; + } + .message { + min-width: 0; + color: var(--text-strong); + overflow-wrap: anywhere; + white-space: pre-wrap; + } + .times { + flex-shrink: 0; + color: var(--danger-text); + font-weight: 600; + font-variant-numeric: tabular-nums; + } + .meta { + display: flex; + flex-wrap: wrap; + gap: 6px 10px; + align-items: center; + min-width: 0; + font-size: 12px; + overflow-wrap: anywhere; + } + .chip { + display: inline-flex; + align-items: center; + padding: 1px 8px; + border: 1px solid transparent; + border-radius: 99px; + font-size: 11px; + font-weight: 600; + @include m.tint(accent); + } + .chip[data-tone='danger'] { + @include m.tint(danger); + } + .chip[data-tone='warn'] { + @include m.tint(warn); + } + .chip.plain { + border-color: var(--border); + background: var(--surface-2); + color: var(--text-2); + } + details summary { + width: fit-content; + border-radius: 2px; + color: var(--text-2); + font-size: 12px; + cursor: pointer; + } + details summary:focus-visible { + @include m.focus-ring; + } + pre { + max-height: 240px; + margin: 6px 0 0; + padding: 8px; + overflow: auto; + border-radius: var(--radius-sm); + background: var(--bg); + color: var(--text); + white-space: pre; + } + pre:focus-visible { + @include m.focus-ring(-2px); + } + .small { + height: var(--control-h); + padding: 0 12px; + border: 1px solid var(--control-border); + border-radius: var(--radius-sm); + background: var(--surface-2); + color: var(--text); + } + .small:focus-visible { + @include m.focus-ring; + } + `, +}) +export class ServerErrorsSection { + readonly rpc = input(null); + + private readonly destroyRef = inject(DestroyRef); + private unsubscribe?: () => void; + + protected readonly time = time; + protected readonly place = serverErrorPlace; + + readonly errors = signal([]); + readonly status = signal(''); + + constructor() { + effect(() => { + const client = this.rpc(); + if (client) void this.load(client); + }); + this.destroyRef.onDestroy(() => this.unsubscribe?.()); + } + + async clear() { + const client = this.rpc(); + if (!client) return; + try { + await client.scope('pangular').rpc.call('clear-server-errors'); + this.status.set('Server errors cleared.'); + } catch { + this.status.set("Couldn't clear the server errors."); + } + } + + private async load(client: DevframeRpcClient) { + try { + const state = await client.scope('pangular').rpc.sharedState('server-errors'); + if (this.destroyRef.destroyed) return; + const apply = (value: unknown) => this.errors.set(serverErrorsOf(value)); + apply(state.value()); + this.unsubscribe?.(); + this.unsubscribe = state.on('updated', apply); + } catch { + // An older server has no server errors; the section stays hidden. + } + } +} diff --git a/apps/docs/src/content/agents/tools.md b/apps/docs/src/content/agents/tools.md index 789fb7dd..f9771055 100644 --- a/apps/docs/src/content/agents/tools.md +++ b/apps/docs/src/content/agents/tools.md @@ -4,7 +4,7 @@ description: Every agent tool the devtools expose, grouped by inspector, with wh --- - Ninety-eight tools, grouped by inspector. Each one answers a question you would otherwise answer by clicking through the panel. + Ninety-nine tools, grouped by inspector. Each one answers a question you would otherwise answer by clicking through the panel. # Tools @@ -927,11 +927,14 @@ These tools read what a Capacitor app reported: its platform, its plugins and th These tools read what a page captured: errors from Angular's `ErrorHandler`, uncaught errors, unhandled promise rejections and console calls. See [Errors](../inspectors/errors.md). Reads: page. -| Tool | What it answers | Arguments | -| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | -| `list-errors` | Error groups, most recent first: id, count, when last seen, the Angular error code linked to its reference page, the message, the owning component with its instance id, and the source (`handler`, `window` or `promise`). | `code`, `limit` | -| `explain-error` | One error by `id`: message, count, sources, the owning component, the stack of the first occurrence, and for an Angular code its reference page and a short known cause. With `code` instead, the reference entry and the errors that carry it. An NG0100, a hydration mismatch or a missing provider adds its explainer. | `id`, `code` | -| `list-console` | Console calls, newest first: when, level, the text of the arguments and the Angular code it mentions. Calls repeated in a row show once with a count. | `level`, `search`, `since`, `limit` | +| Tool | What it answers | Arguments | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | +| `list-errors` | Error groups, most recent first: id, count, when last seen, the Angular error code linked to its reference page, the message, the owning component with its instance id, and the source (`handler`, `window` or `promise`). | `code`, `limit` | +| `explain-error` | One error by `id`: message, count, sources, the owning component, the stack of the first occurrence, and for an Angular code its reference page and a short known cause. With `code` instead, the reference entry and the errors that carry it. An NG0100, a hydration mismatch or a missing provider adds its explainer. | `id`, `code` | +| `list-console` | Console calls, newest first: when, level, the text of the arguments and the Angular code it mentions. Calls repeated in a row show once with a count. | `level`, `search`, `since`, `limit` | +| `list-server-errors` | Errors on the dev server that no page reports, most recent first: SSR errors (a `console.error` during a server render, or Angular's `ErrorHandler` on the server) and Vite transform and compile errors. With `id`, one group with its message, Angular code, code frame and stack. | `kind`, `id`, `limit` | + +`list-server-errors` reads what the server captured in its own process, so it needs the Express hub or the Vite plugin, not the stdio MCP server; Vite errors need the Vite plugin. `kind` is `ssr` or `vite`, and `limit` goes up to 100 (default 30). A group expires 30 minutes after its last repeat. See [Server errors](../inspectors/errors.md#server-errors). The explainers are the ones the [Errors](../inspectors/errors.md#explainers) tab shows. For NG0100: the binding, the template function, the previous and current value, the component fields that hold the new value, and the named signals written with it just before the check, with the code that wrote them when write stacks are on. For NG0500 to NG0503: a diff of the server HTML Angular expected and the client DOM it found. For NG0201 and `NullInjectorError`: the token and Angular's dependency path, to pass to `explain-injection`. diff --git a/apps/docs/src/content/inspectors/errors.md b/apps/docs/src/content/inspectors/errors.md index ed0bdad2..3f7a6711 100644 --- a/apps/docs/src/content/inspectors/errors.md +++ b/apps/docs/src/content/inspectors/errors.md @@ -67,6 +67,15 @@ Click **Console** to see the page's `console.log`, `info`, `warn`, `error` and ` The page keeps capturing while nobody watches, and sends its errors and console calls only while a panel is open or an agent called a tool in the last `limits.idleAfterMs`. An agent call to an idle page gets what the page captured meanwhile. See [Performance](../getting-started/configuration.md#performance). +### Server errors + +Above the page's errors, **Server errors** lists what the dev server hit that no page can report, most recent first, with a count for repeats: + +- **SSR**: a server render that logged an error with `console.error`, such as Angular's `ErrorHandler` on the server (`console.error('ERROR', error)`). Inside a render that `ssrMiddleware` or `withPangularSsr()` traced, any `console.error` counts, and the row names the page it rendered. Outside one, only Angular's `ErrorHandler` format counts, so the server's own logs stay out. +- **Vite**: the transform, compile and SSR module errors Vite shows in its browser overlay, with the file, line, plugin and code frame. Only with the [Vite plugin](../getting-started/vite.md). + +Open **Code frame** or **Stack** for the details. **Clear server errors** empties the list. A group expires 30 minutes after its last repeat, and the list keeps the last 100 groups. Messages, frames and stacks are masked like other live values. The section shows only while there is a server error. + ### Pages When several tabs report, **Page** picks one, or **All pages** to see them together. The panel of a tab starts on that tab. @@ -103,12 +112,13 @@ A `RuntimeError` carries its code, and other errors carry it in the message. The ## Agent tools -| Tool | What it does | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `pangular:list-errors` | The error groups, with count, code link, owning component and source. | -| `pangular:explain-error` | One error by `id`, or an Angular `code`: the message, the owning component, the stack, the reference page and cause, and the explainer of an NG0100, a hydration mismatch or a missing provider. | -| `pangular:list-console` | The console calls, by `level`, `search` or `since`. | -| `pangular:explain-injection` | Walks the lookup of a token from a component, with `self`, `skipSelf`, `host` and `optional`. Belongs to the [Injectors](./injectors.md#agent-tools) inspector. | +| Tool | What it does | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `pangular:list-errors` | The error groups, with count, code link, owning component and source. | +| `pangular:explain-error` | One error by `id`, or an Angular `code`: the message, the owning component, the stack, the reference page and cause, and the explainer of an NG0100, a hydration mismatch or a missing provider. | +| `pangular:list-console` | The console calls, by `level`, `search` or `since`. | +| `pangular:list-server-errors` | The dev server's SSR and Vite errors, by `kind`, or one by `id` with its code frame and stack. | +| `pangular:explain-injection` | Walks the lookup of a token from a component, with `self`, `skipSelf`, `host` and `optional`. Belongs to the [Injectors](./injectors.md#agent-tools) inspector. | The known causes come from Angular's error reference, in a sentence or two. For a code without a reference page, `explain-error` says so. See [Tools](../agents/tools.md#errors). diff --git a/packages/devtools/CHANGELOG.md b/packages/devtools/CHANGELOG.md index dd6897b8..38fa0c5b 100644 --- a/packages/devtools/CHANGELOG.md +++ b/packages/devtools/CHANGELOG.md @@ -148,7 +148,7 @@ What remains: `/config` exports `DEFAULT_POPUP_HOTKEY`, `PANGULAR_ACTIONS`, `PAN - **NgRx Store:** full state for every entry still in the log, session export and import, generated tests, and NGXS, Elf and MiniRx through their Redux DevTools integration. - **TanStack Query:** a tab for queries and mutations, with invalidate and refetch. - **SSR & HTTP:** the page's own `fetch()` calls next to the `HttpClient` ones (#369), a network waterfall, search and filters on the timeline, response preview format, copy and mock, TransferState keys and edits in SSR overrides, and SSR tracing for fetch handlers such as Hono, h3 and Nitro. -- **Errors:** a tab for `ErrorHandler` errors, uncaught errors, unhandled rejections and the console, with NG0xxx codes and explainers for NG0100, hydration mismatches and missing providers. +- **Errors:** a tab for `ErrorHandler` errors, uncaught errors, unhandled rejections and the console, with NG0xxx codes and explainers for NG0100, hydration mismatches and missing providers. **Server errors** lists SSR render errors and Vite transform and compile errors, and `list-server-errors` reads them (#372). - **Timeline:** one time axis with layers for the router, HTTP, NgRx, change detection, forms and signals. - **Audit:** one ranked list of findings across the inspectors, an axe pass, and before and after sessions. - **Capacitor:** a tab for the platform, plugins, plugin calls and listeners of a Capacitor app. @@ -159,7 +159,7 @@ What remains: `/config` exports `DEFAULT_POPUP_HOTKEY`, `PANGULAR_ACTIONS`, `PAN #### Agents and MCP -- 98 agent tools. New tools include `audit`, `snapshot`, `wait-for`, `scan-templates` (#415), `list-errors`, `explain-error`, `list-console`, `find-leaks`, `diagnose-change-detection` (#395), `set-component-property`, `set-signal`, `track-subscriptions`, `watch-signal`, `hydration`, `screenshot-component`, `open-in-panel`, `startup-timing`, `read-provider-value`, `record-change-detection`, `probe-route`, `record-guards`, the TanStack Query, Capacitor, plugin and NgRx session tools, and tools for HTTP fault rules and SSR overrides. +- 99 agent tools. New tools include `audit`, `snapshot`, `wait-for`, `scan-templates` (#415), `list-errors`, `explain-error`, `list-console`, `list-server-errors`, `find-leaks`, `diagnose-change-detection` (#395), `set-component-property`, `set-signal`, `track-subscriptions`, `watch-signal`, `hydration`, `screenshot-component`, `open-in-panel`, `startup-timing`, `read-provider-value`, `record-change-detection`, `probe-route`, `record-guards`, the TanStack Query, Capacitor, plugin and NgRx session tools, and tools for HTTP fault rules and SSR overrides. - One naming scheme for tools and arguments, and plain markdown answers. See **Agent tools renamed**. - Tool titles, inspector tags and safety tiers (`action` and `destructive`). - Every answer names the page it read, and one rule picks the default page for every tool. diff --git a/packages/devtools/src/__tests__/docs-tool-count.test.ts b/packages/devtools/src/__tests__/docs-tool-count.test.ts index bce5d562..62f0510b 100644 --- a/packages/devtools/src/__tests__/docs-tool-count.test.ts +++ b/packages/devtools/src/__tests__/docs-tool-count.test.ts @@ -57,6 +57,27 @@ const WORDS = [ 'Ninety-seven', 'Ninety-eight', 'Ninety-nine', + 'One hundred', + 'One hundred and one', + 'One hundred and two', + 'One hundred and three', + 'One hundred and four', + 'One hundred and five', + 'One hundred and six', + 'One hundred and seven', + 'One hundred and eight', + 'One hundred and nine', + 'One hundred and ten', + 'One hundred and eleven', + 'One hundred and twelve', + 'One hundred and thirteen', + 'One hundred and fourteen', + 'One hundred and fifteen', + 'One hundred and sixteen', + 'One hundred and seventeen', + 'One hundred and eighteen', + 'One hundred and nineteen', + 'One hundred and twenty', ]; it('states on the tools page how many tools the server registers', async () => { @@ -77,7 +98,7 @@ it('states on the tools page how many tools the server registers', async () => { join(__dirname, '../../../../apps/docs/src/content/agents/tools.md'), 'utf-8', ); - const stated = /^\s*([A-Z][a-z]+(?:-[a-z]+)?) tools, grouped by inspector/m.exec(page)?.[1]; + const stated = /^\s*([A-Z][a-z]+(?:[- ][a-z]+)*) tools, grouped by inspector/m.exec(page)?.[1]; expect(stated).toBeDefined(); const index = WORDS.indexOf(stated!); expect(index).toBeGreaterThanOrEqual(0); diff --git a/packages/devtools/src/__tests__/fixtures/agent-metadata-derived.json b/packages/devtools/src/__tests__/fixtures/agent-metadata-derived.json index 1b7f3b6f..5967e71f 100644 --- a/packages/devtools/src/__tests__/fixtures/agent-metadata-derived.json +++ b/packages/devtools/src/__tests__/fixtures/agent-metadata-derived.json @@ -43,6 +43,7 @@ "list-http-rules": "http", "list-plugins": "plugins", "list-routes": "router", + "list-server-errors": "errors", "list-ssr-overrides": "http", "list-ssr-requests": "http", "navigate": "router", @@ -178,6 +179,7 @@ "list-pages", "list-plugins", "list-routes", + "list-server-errors", "list-ssr-overrides", "list-ssr-requests", "navigate", @@ -271,6 +273,7 @@ "list-pages": null, "list-plugins": ["plugins"], "list-routes": ["router"], + "list-server-errors": ["errors"], "list-ssr-overrides": ["http"], "list-ssr-requests": ["http"], "navigate": ["router"], diff --git a/packages/devtools/src/__tests__/server-errors.test.ts b/packages/devtools/src/__tests__/server-errors.test.ts new file mode 100644 index 00000000..97e2d415 --- /dev/null +++ b/packages/devtools/src/__tests__/server-errors.test.ts @@ -0,0 +1,245 @@ +import { createHostContext } from 'devframe/node'; +import { afterEach, describe, expect, it } from 'vitest'; +import pangular, { createPangular } from '../devframe.ts'; +import { + captureSsrConsoleErrors, + recordServerError, + serverErrorsRegistry, + ssrErrorFromConsole, + takeServerErrors, + viteErrorFromPayload, + watchViteErrors, + type ServerErrorInput, +} from '../server-errors.ts'; +import { + MAX_SERVER_ERRORS, + SERVER_ERROR_TTL_MS, + listServerErrorsText, + mergeServerError, + staleServerErrors, + type ServerErrorsState, +} from '../rpc/server-errors-register.ts'; +import { runInSsrRequest } from '../ssr-context.ts'; +import { ssrRegistry } from '../ssr-registry.ts'; +import { answerText } from './tool-answer.ts'; + +const JWT = ['eyJhbGciOiJIUzI1NiJ9', 'eyJzdWIiOiIxMjM0NSJ9', 'c2lnbmF0dXJlc2ln'].join('.'); + +const vitePayload = (err: Record) => ({ type: 'error', err }); + +afterEach(() => { + serverErrorsRegistry().stopConsole?.(); + delete (globalThis as { __PANGULAR_SERVER_ERRORS__?: unknown }).__PANGULAR_SERVER_ERRORS__; + ssrRegistry().active.clear(); +}); + +describe('Vite errors', () => { + it('reads an error payload and ignores other payloads', () => { + expect( + viteErrorFromPayload( + vitePayload({ + message: 'Transform failed with 1 error', + stack: 'Error: Transform failed\n at esbuild', + plugin: 'vite:esbuild', + id: '/app/src/main.ts?v=1', + frame: '1 | const a = ;', + loc: { file: '/app/src/main.ts?v=1', line: 1, column: 10 }, + }), + ), + ).toEqual({ + kind: 'vite', + message: 'Transform failed with 1 error', + stack: 'Error: Transform failed\n at esbuild', + plugin: 'vite:esbuild', + file: '/app/src/main.ts', + line: 1, + column: 10, + frame: '1 | const a = ;', + }); + expect(viteErrorFromPayload({ type: 'update', updates: [] })).toBeUndefined(); + expect(viteErrorFromPayload(vitePayload({}))).toBeUndefined(); + expect(viteErrorFromPayload(null)).toBeUndefined(); + }); + + it('wraps ws.send, passes every payload on and puts send back', () => { + const sent: unknown[] = []; + const server = { ws: { send: (payload: unknown) => void sent.push(payload) } }; + const original = server.ws.send; + const recorded: ServerErrorInput[] = []; + takeServerErrors((error) => recorded.push(error)); + const stop = watchViteErrors(server); + server.ws.send(vitePayload({ message: 'Failed to resolve import "./missing"' })); + server.ws.send({ type: 'full-reload' }); + expect(sent).toHaveLength(2); + expect(recorded.map((e) => e.message)).toEqual(['Failed to resolve import "./missing"']); + stop(); + expect(server.ws.send).toBe(original); + }); +}); + +describe('SSR errors', () => { + it("takes Angular's ErrorHandler format and calls inside a traced render", () => { + const error = new Error('NG0201: No provider found for `TripsApi`'); + expect(ssrErrorFromConsole(['ERROR', error])).toMatchObject({ + kind: 'ssr', + message: 'NG0201: No provider found for `TripsApi`', + stack: error.stack, + }); + // A plain log outside a render is not an SSR error. + expect(ssrErrorFromConsole(['Server listening'])).toBeUndefined(); + ssrRegistry().active.set('r1', { url: '/trips', fetches: 0, fetchMs: 0 }); + const inside = runInSsrRequest('r1', () => ssrErrorFromConsole(['Render failed', error])); + expect(inside).toMatchObject({ kind: 'ssr', url: '/trips', requestId: 'r1' }); + expect(inside!.message).toContain('Render failed'); + }); + + it('listens to console.error and still prints the call', () => { + const printed: unknown[][] = []; + const original = console.error; + console.error = (...args: unknown[]) => void printed.push(args); + try { + const recorded: ServerErrorInput[] = []; + takeServerErrors((error) => recorded.push(error)); + const stop = captureSsrConsoleErrors(); + expect(captureSsrConsoleErrors()).toBe(stop); + console.error('ERROR', new Error('boom')); + console.error('unrelated'); + expect(printed).toHaveLength(2); + expect(recorded.map((e) => e.message)).toEqual(['boom']); + stop(); + console.error('ERROR', new Error('after')); + expect(recorded).toHaveLength(1); + } finally { + console.error = original; + } + }); + + it('keeps the last errors until the devframe server takes them', () => { + for (let i = 0; i < 25; i++) recordServerError({ kind: 'vite', message: `e${i}` }); + const taken: string[] = []; + const stop = takeServerErrors((error) => taken.push(error.message)); + expect(taken).toHaveLength(20); + expect(taken[0]).toBe('e5'); + recordServerError({ kind: 'ssr', message: 'live' }); + expect(taken.at(-1)).toBe('live'); + stop(); + recordServerError({ kind: 'ssr', message: 'kept' }); + expect(serverErrorsRegistry().pending.map((e) => e.message)).toEqual(['kept']); + }); +}); + +describe('server errors state', () => { + const empty = (): ServerErrorsState => ({ errors: [], dropped: 0 }); + let n = 0; + const id = () => `s${++n}`; + + it('groups repeats, moves them first and masks secrets', () => { + const state = empty(); + mergeServerError(state, { kind: 'ssr', message: `Bad token ${JWT}`, url: '/a', at: 1 }, id); + mergeServerError(state, { kind: 'vite', message: 'Syntax error', file: '/x.ts', at: 2 }, id); + mergeServerError(state, { kind: 'ssr', message: `Bad token ${JWT}`, url: '/a', at: 3 }, id); + expect(state.errors.map((g) => [g.kind, g.count, g.lastAt])).toEqual([ + ['ssr', 2, 3], + ['vite', 1, 2], + ]); + expect(state.errors[0]!.message).not.toContain(JWT); + expect(state.errors[0]!.firstAt).toBe(1); + }); + + it('reads the Angular code, drops the oldest groups at the cap and expires stale ones', () => { + const state = empty(); + for (let i = 0; i <= MAX_SERVER_ERRORS; i++) { + mergeServerError(state, { kind: 'vite', message: `e${i}`, at: i }, id); + } + expect(state.errors).toHaveLength(MAX_SERVER_ERRORS); + expect(state.dropped).toBe(1); + mergeServerError( + state, + { kind: 'ssr', message: 'NG0100: changed', at: SERVER_ERROR_TTL_MS }, + id, + ); + expect(state.errors[0]!.code).toBe('NG0100'); + const stale = staleServerErrors(state, SERVER_ERROR_TTL_MS + 50); + expect(stale).toContain(state.errors.at(-1)!.id); + expect(stale).not.toContain(state.errors[0]!.id); + }); + + it('lists groups and shows one with its frame and stack', () => { + const state = empty(); + mergeServerError( + state, + { + kind: 'vite', + message: 'Unexpected token', + file: '/src/app.ts', + line: 3, + column: 7, + plugin: 'vite:esbuild', + frame: '3 | let = 1', + stack: 'at transform', + at: Date.now(), + }, + () => 'v1', + ); + const list = listServerErrorsText(state); + expect(list).toContain('`v1`'); + expect(list).toContain('`/src/app.ts:3:7`'); + expect(listServerErrorsText(state, { kind: 'ssr' })).toMatch(/^No SSR errors/); + const one = listServerErrorsText(state, { id: 'v1' }); + expect(one).toContain('3 | let = 1'); + expect(one).toContain('at transform'); + expect(listServerErrorsText(state, { id: 'nope' })).toMatch(/No server error with the id/); + }); +}); + +async function boot(definition = pangular) { + const host = { + mountStatic: () => {}, + resolveOrigin: () => 'http://localhost', + getStorageDir: () => '', + }; + const ctx = await createHostContext({ cwd: process.cwd(), mode: 'dev', host: host as never }); + await definition.setup(ctx as never); + const call = (name: string) => + (ctx.rpc.invokeLocal as (name: string) => Promise)(`pangular:${name}`); + const invoke = async (tool: string, args: Record = {}) => + await answerText(ctx.agent.invoke(`pangular:${tool}`, args)); + const state = async () => + ( + await ( + ctx.rpc as unknown as { + sharedState: { get: (key: string) => Promise<{ value: () => ServerErrorsState }> }; + } + ).sharedState.get('pangular:server-errors') + ).value(); + return { ctx, call, invoke, state }; +} + +describe('server errors on the devframe server', () => { + it('takes captured errors into shared state, lists and clears them', async () => { + recordServerError({ kind: 'vite', message: 'Before the server started' }); + const { invoke, state, call } = await boot(); + recordServerError({ kind: 'ssr', message: 'During a render', url: '/trips' }); + expect((await state()).errors.map((g) => g.message)).toEqual([ + 'During a render', + 'Before the server started', + ]); + expect(await invoke('list-server-errors')).toContain('During a render'); + await call('clear-server-errors'); + expect((await state()).errors).toEqual([]); + }); + + it('is a read tool that disappears with the errors inspector', async () => { + const off = await boot(createPangular({ inspectors: { errors: false } })); + expect(off.ctx.agent.list().tools.map((t) => t.id)).not.toContain( + 'pangular:list-server-errors', + ); + await expect(off.call('clear-server-errors')).rejects.toThrow(); + // With the inspector off nothing takes the errors, so they wait. + recordServerError({ kind: 'vite', message: 'waiting' }); + expect(serverErrorsRegistry().pending.some((e) => e.message === 'waiting')).toBe(true); + const { ctx } = await boot(); + const tool = ctx.agent.list().tools.find((t) => t.id === 'pangular:list-server-errors'); + expect(tool?.safety).toBe('read'); + }); +}); diff --git a/packages/devtools/src/__tests__/vite-errors.test.ts b/packages/devtools/src/__tests__/vite-errors.test.ts new file mode 100644 index 00000000..87e51df9 --- /dev/null +++ b/packages/devtools/src/__tests__/vite-errors.test.ts @@ -0,0 +1,43 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { serverErrorsRegistry } from '../server-errors.ts'; + +vi.mock('../hub.ts', () => ({ + PANGULAR_HUB_BASE: '/__devframes/', + startPangularHub: () => ({ nodeMiddleware: vi.fn(), close: vi.fn() }), +})); + +const { default: pangularVite } = await import('../vite.ts'); + +function serve(options: Parameters[0]) { + const sent: unknown[] = []; + const ws = { send: (payload: unknown) => void sent.push(payload) }; + const original = ws.send; + const plugin = pangularVite({ apiPrefix: 'api', ...options }); + (plugin.configureServer as (server: unknown) => void)({ + config: { root: process.cwd(), server: {}, logger: { warn: vi.fn(), info: vi.fn() } }, + middlewares: { use: vi.fn() }, + httpServer: null, + ws, + }); + return { ws, sent, original }; +} + +afterEach(() => { + delete (globalThis as { __PANGULAR_SERVER_ERRORS__?: unknown }).__PANGULAR_SERVER_ERRORS__; +}); + +describe('Vite plugin error capture', () => { + it("records Vite's overlay errors and still sends them", () => { + const { ws, sent } = serve({}); + ws.send({ type: 'error', err: { message: 'Transform failed', plugin: 'vite:esbuild' } }); + expect(sent).toHaveLength(1); + expect(serverErrorsRegistry().pending.map((e) => [e.kind, e.message])).toEqual([ + ['vite', 'Transform failed'], + ]); + }); + + it('leaves ws.send alone with the errors inspector off', () => { + const { ws, original } = serve({ inspectors: { errors: false } }); + expect(ws.send).toBe(original); + }); +}); diff --git a/packages/devtools/src/config.ts b/packages/devtools/src/config.ts index 277ad62e..a29b1660 100644 --- a/packages/devtools/src/config.ts +++ b/packages/devtools/src/config.ts @@ -1239,6 +1239,8 @@ export const AGENT_ENTRIES: Readonly> = { 'list-errors': { kind: 'tool', inspectors: ['errors'], page: true }, 'explain-error': { kind: 'tool', inspectors: ['errors'], page: true }, 'list-console': { kind: 'tool', inspectors: ['errors'], page: true }, + // SSR and Vite errors the server captured in its own process; the stdio MCP server has none. + 'list-server-errors': { kind: 'tool', inspectors: ['errors'], page: true }, // plugins 'list-plugins': { kind: 'tool', inspectors: ['plugins'], page: true }, @@ -1467,6 +1469,7 @@ export const RPC_INSPECTOR: Record = { 'forget-capacitor-page': 'capacitor', 'push-errors': 'errors', 'forget-errors-page': 'errors', + 'clear-server-errors': 'errors', 'push-analog': 'analog', 'forget-analog-page': 'analog', 'analog-clear-calls': 'analog', @@ -1517,6 +1520,7 @@ export const SHARED_STATE_INSPECTOR: Record = { capacitor: 'capacitor', analog: 'analog', errors: 'errors', + 'server-errors': 'errors', startup: 'injectors', plugins: 'plugins', }; diff --git a/packages/devtools/src/devframe.ts b/packages/devtools/src/devframe.ts index 415a284b..2a55611c 100644 --- a/packages/devtools/src/devframe.ts +++ b/packages/devtools/src/devframe.ts @@ -23,6 +23,7 @@ import { } from './rpc/hydration-tools.ts'; import { registerConsoleGlobals } from './rpc/console-globals.ts'; import { registerErrors } from './rpc/errors-register.ts'; +import { registerServerErrors } from './rpc/server-errors-register.ts'; import { registerSnapshot } from './rpc/snapshot-tools.ts'; import { registerLeaks } from './rpc/leaks-register.ts'; import { registerReduxStores } from './rpc/redux-stores-register.ts'; @@ -944,6 +945,10 @@ const pangular = defineDevframe({ ttl: liveTtl, }) : null; + // SSR and Vite errors of this process (`server-errors.ts`); no page reports them. + const serverErrors = on.errors + ? await registerServerErrors({ my, register, agent, capture: pageTools }) + : null; const leaksInspector = on.components ? await registerLeaks({ my, @@ -1475,6 +1480,7 @@ const pangular = defineDevframe({ if (expireNgrxPages(ngrxPages, now, liveTtl)) applyNgrx(); if (expireCapacitorPages(capacitorPages, now, liveTtl)) applyCapacitor(); errorsInspector?.expire(now); + serverErrors?.expire(now); leaksInspector?.expire(now); reduxStores?.expire(now); tanstackQuery?.expire(now); @@ -1497,6 +1503,7 @@ const pangular = defineDevframe({ clearInterval(expiry); clearInterval(interestTick); pluginsInspector?.dispose(); + serverErrors?.dispose(); interest.stop(); clearTimeout(flushTimer); registry.record = undefined; @@ -1549,7 +1556,7 @@ const pangular = defineDevframe({ id: 'pangular:list-http-calls', title: 'List HTTP calls', description: - 'HttpClient calls recorded by withPangular() and the page\'s own fetch() calls (flag `native fetch`), newest first: side, method, URL, status, duration, rule and cache flags, and the response preview. `view: "waterfall"` draws a page\'s client calls per navigation instead. `headers` adds headers. Masked. Empty without the interceptor or a page.', + 'HttpClient calls from withPangular() and native fetch() calls, newest first: side, method, URL, status, duration, flags and response preview. `view: "waterfall"` draws a page\'s client calls per navigation. `headers` adds headers. Masked. Empty without the interceptor or a page.', inputSchema: { type: 'object', properties: { diff --git a/packages/devtools/src/rpc/server-errors-register.ts b/packages/devtools/src/rpc/server-errors-register.ts new file mode 100644 index 00000000..864645a0 --- /dev/null +++ b/packages/devtools/src/rpc/server-errors-register.ts @@ -0,0 +1,284 @@ +import { createSharedState } from 'devframe/utils/shared-state'; +import { ngCodeOf } from '../ng-error-codes.ts'; +import { redactMessage, redactUrl } from '../router.ts'; +import { + captureSsrConsoleErrors, + takeServerErrors, + type ServerErrorInput, + type ServerErrorKind, +} from '../server-errors.ts'; +import { clip } from '../text.ts'; +import { code, untrusted } from './forms-tools.ts'; + +type AnyRecord = Record; + +/** The part of devframe's shared state used here. */ +interface ServerErrorsShared { + value(): ServerErrorsState; + mutate(recipe: (draft: ServerErrorsState) => void): void; +} + +/** One group of server errors with the same kind, message and place. */ +export interface ServerErrorGroup { + id: string; + kind: ServerErrorKind; + message: string; + count: number; + firstAt: number; + lastAt: number; + code?: string; + stack?: string; + url?: string; + requestId?: string; + plugin?: string; + file?: string; + line?: number; + column?: number; + frame?: string; +} + +export interface ServerErrorsState { + /** Most recent first. */ + errors: ServerErrorGroup[]; + /** Groups dropped at {@link MAX_SERVER_ERRORS} since the last clear. */ + dropped: number; +} + +/** Groups kept; older ones are dropped. */ +export const MAX_SERVER_ERRORS = 100; +/** A group with no repeat for this long expires, like page data does. */ +export const SERVER_ERROR_TTL_MS = 30 * 60_000; + +const mask = (text: string | undefined, max: number) => + text ? clip(redactMessage(text.slice(0, max * 2)), max) : undefined; + +/** Masks and caps one captured error, as it is stored and shown. */ +export function maskServerError( + error: ServerErrorInput, +): Omit { + const message = mask(error.message, 1000) ?? ''; + const ngCode = ngCodeOf(undefined, message); + return { + kind: error.kind === 'vite' ? 'vite' : 'ssr', + message, + ...(ngCode ? { code: ngCode } : {}), + ...(error.stack ? { stack: mask(error.stack, 8000) } : {}), + ...(error.url ? { url: redactUrl(error.url.slice(0, 2000)) } : {}), + ...(error.requestId ? { requestId: error.requestId.slice(0, 40) } : {}), + ...(error.plugin ? { plugin: error.plugin.slice(0, 200) } : {}), + ...(error.file ? { file: redactUrl(error.file.slice(0, 2000)) } : {}), + ...(error.line !== undefined ? { line: error.line } : {}), + ...(error.column !== undefined ? { column: error.column } : {}), + ...(error.frame ? { frame: mask(error.frame, 4000) } : {}), + }; +} + +const groupKey = (e: Pick) => + `${e.kind}\u0000${e.message}\u0000${e.file ?? ''}:${e.line ?? ''}\u0000${e.kind === 'ssr' ? (e.url ?? '') : ''}`; + +/** Adds one error to the state, as a new group or a repeat of one. */ +export function mergeServerError( + state: ServerErrorsState, + error: ServerErrorInput, + nextId: () => string, +): void { + const masked = maskServerError(error); + const at = error.at ?? Date.now(); + const key = groupKey(masked); + const index = state.errors.findIndex((group) => groupKey(group) === key); + if (index >= 0) { + const [group] = state.errors.splice(index, 1); + state.errors.unshift({ ...group!, ...masked, count: group!.count + 1, lastAt: at }); + return; + } + state.errors.unshift({ id: nextId(), ...masked, count: 1, firstAt: at, lastAt: at }); + if (state.errors.length > MAX_SERVER_ERRORS) { + state.dropped += state.errors.length - MAX_SERVER_ERRORS; + state.errors.length = MAX_SERVER_ERRORS; + } +} + +/** Ids of groups with no repeat for {@link SERVER_ERROR_TTL_MS}. */ +export function staleServerErrors(state: ServerErrorsState, now = Date.now()): string[] { + return state.errors.filter((group) => now - group.lastAt > SERVER_ERROR_TTL_MS).map((g) => g.id); +} + +function ago(at: number, now: number): string { + const seconds = Math.max(0, Math.round((now - at) / 1000)); + if (seconds < 120) return `${seconds}s ago`; + const minutes = Math.round(seconds / 60); + return `${minutes} min ago`; +} + +const cell = (text: string) => text.replace(/\|/g, '\\|').replace(/\n/g, ' '); + +function placeOf(group: ServerErrorGroup): string { + if (group.kind === 'ssr') return group.url ? code(group.url) : 'a server render'; + if (!group.file) return group.plugin ? code(group.plugin) : ''; + const line = + group.line !== undefined + ? `:${group.line}${group.column !== undefined ? `:${group.column}` : ''}` + : ''; + return `${code(`${group.file}${line}`)}${group.plugin ? ` (${code(group.plugin)})` : ''}`; +} + +/** `list-server-errors`: the dev server's SSR and Vite errors, most recent first. */ +export function listServerErrorsText( + state: ServerErrorsState, + args: { kind?: unknown; id?: unknown; limit?: unknown } = {}, + now = Date.now(), +): string { + const kind = args.kind === 'ssr' || args.kind === 'vite' ? args.kind : undefined; + if (typeof args.id === 'string' && args.id) { + const group = state.errors.find((g) => g.id === args.id); + if (!group) { + return `No server error with the id ${code(args.id)}. Call pangular:list-server-errors without \`id\` for the current ones; groups expire 30 minutes after their last repeat.`; + } + const parts = [ + `**${group.kind === 'ssr' ? 'SSR' : 'Vite'} error ${code(group.id)}**, ${group.count} time(s), last ${ago(group.lastAt, now)}, at ${placeOf(group) || 'an unknown place'}.`, + '', + '```text', + group.message, + '```', + ]; + if (group.code) + parts.push( + '', + `Angular code ${code(group.code)}: pass it to pangular:explain-error for the known cause.`, + ); + if (group.requestId) + parts.push( + '', + `SSR request ${code(group.requestId)}: pangular:explain-ssr-request shows its render.`, + ); + if (group.frame) parts.push('', 'Code frame:', '', '```text', group.frame, '```'); + if (group.stack) parts.push('', 'Stack:', '', '```text', group.stack, '```'); + return untrusted(parts.join('\n')); + } + const all = state.errors.filter((group) => !kind || group.kind === kind); + const limit = Math.min(Math.max(Math.floor(Number(args.limit) || 30), 1), MAX_SERVER_ERRORS); + if (!all.length) { + const what = kind === 'ssr' ? 'SSR errors' : kind === 'vite' ? 'Vite errors' : 'server errors'; + return `No ${what} in the last 30 minutes. SSR errors are what the server logs with console.error during a render (Angular's ErrorHandler); Vite errors are the transform and compile errors Vite shows in its browser overlay, captured only with the Vite plugin.`; + } + const rows = all + .slice(0, limit) + .map( + (group) => + `| ${code(group.id)} | ${group.kind === 'ssr' ? 'SSR' : 'Vite'} | ${group.count} | ${ago(group.lastAt, now)} | ${cell(code(clip(group.message, 200)))} | ${cell(placeOf(group))} |`, + ); + const more = + all.length > limit ? `\n\n${all.length - limit} more; raise \`limit\` to see them.` : ''; + const dropped = state.dropped + ? `\n\n${state.dropped} older group(s) were dropped to stay under ${MAX_SERVER_ERRORS}.` + : ''; + return untrusted( + `${all.length} server error group(s), most recent first. Pass an \`id\` for the message, code frame and stack.\n\n| Id | Kind | Count | Last | Message | Where |\n| --- | --- | --- | --- | --- | --- |\n${rows.join('\n')}${more}${dropped}`, + ); +} + +interface Scoped { + rpc: { sharedState(name: string, options: AnyRecord): Promise }; +} + +/** + * devframe's RPC and agent definitions, as loose as `errors-register.ts` + * takes them: the devframe server passes its own typed `register`. + */ +// oxlint-disable-next-line typescript/no-explicit-any +type Definition = Record; + +export interface ServerErrorsHost { + my: Scoped; + /** Registers an RPC function, skipped when the Errors inspector is off. */ + register(definition: Definition): void; + agent: { registerTool(tool: Definition): void }; + /** Captures in this process. Off for the stdio MCP server, which renders nothing. */ + capture: boolean; +} + +export interface ServerErrorsRegistration { + /** Drops groups past their expiry; call it from the shared expiry interval. */ + expire(now?: number): void; + state(): ServerErrorsState; + /** Stops the captures this registration started. */ + dispose(): void; +} + +/** + * The server errors on the devframe server: the `server-errors` shared + * state, the `clear-server-errors` RPC function and the + * `list-server-errors` agent tool. It takes what `server-errors.ts` captured + * in this process. + */ +export async function registerServerErrors( + host: ServerErrorsHost, +): Promise { + const shared = (await host.my.rpc.sharedState('server-errors', { + sharedState: createSharedState({ + initialValue: { errors: [], dropped: 0 }, + enablePatches: true, + }), + })) as unknown as ServerErrorsShared; + const state = () => shared.value(); + let seq = 0; + const nextId = () => `s${++seq}`; + const stops: (() => void)[] = []; + if (host.capture) { + stops.push(captureSsrConsoleErrors()); + stops.push( + takeServerErrors((error) => + shared.mutate((draft: ServerErrorsState) => mergeServerError(draft, error, nextId)), + ), + ); + } + + host.register({ + name: 'clear-server-errors', + type: 'action', + jsonSerializable: true, + handler: () => { + shared.mutate((draft: ServerErrorsState) => { + draft.errors = []; + draft.dropped = 0; + }); + }, + }); + + host.agent.registerTool({ + id: 'pangular:list-server-errors', + title: 'List server errors', + description: + "Dev server errors no page reports, newest first: SSR errors (console.error during a render, Angular's ErrorHandler) and Vite transform errors (Vite plugin only). Repeats grouped; `id` shows one with its code frame and stack. Masked; expire 30 min after the last repeat.", + inputSchema: { + type: 'object', + properties: { + kind: { + type: 'string', + enum: ['ssr', 'vite'], + description: 'Only SSR or only Vite errors.', + }, + id: { + type: 'string', + description: 'A group id from the list, such as s3, for its details.', + }, + limit: { type: 'integer', description: 'How many groups to list (1-100, default 30).' }, + }, + }, + handler: async (args: AnyRecord) => ({ markdown: listServerErrorsText(state(), args ?? {}) }), + }); + + return { + state, + expire: (now = Date.now()) => { + const stale = staleServerErrors(state(), now); + if (!stale.length) return; + shared.mutate((draft: ServerErrorsState) => { + draft.errors = draft.errors.filter((group) => !stale.includes(group.id)); + }); + }, + dispose: () => { + for (const stop of stops.splice(0)) stop(); + }, + }; +} diff --git a/packages/devtools/src/server-errors.ts b/packages/devtools/src/server-errors.ts new file mode 100644 index 00000000..fe98df2e --- /dev/null +++ b/packages/devtools/src/server-errors.ts @@ -0,0 +1,197 @@ +// Errors the dev server hits, which no page can report: a server render that +// throws (Angular's ErrorHandler logs it with `console.error` on the server) +// and Vite's transform, compile and SSR module errors (the payloads Vite +// sends to its browser overlay). They are kept in one registry on +// `globalThis`, so the Vite plugin, the app's server bundle and the devframe +// server share it even when they load separate copies of this package. The +// devframe server sets `record` while the Errors inspector is on; before that +// the registry keeps the last few, so an error from the first render shows up. + +import { tapConsole } from './console-tap.ts'; +import { ssrRegistry } from './ssr-registry.ts'; + +export type ServerErrorKind = 'ssr' | 'vite'; + +/** One error as a capture hands it over, before grouping and masking. */ +export interface ServerErrorInput { + kind: ServerErrorKind; + message: string; + stack?: string; + /** The page path of the server render it happened in. */ + url?: string; + /** The traced SSR request (`ssrMiddleware`), so the SSR & HTTP tab can be cross-checked. */ + requestId?: string; + /** Vite: the plugin that threw, such as `vite:esbuild`. */ + plugin?: string; + /** Vite: the file, without a query. */ + file?: string; + line?: number; + column?: number; + /** Vite: the code frame around the error. */ + frame?: string; + at?: number; +} + +export interface ServerErrorsRegistry { + /** Set by the devframe server while the Errors inspector is on. */ + record?: (error: ServerErrorInput) => void; + /** Errors from before `record` was set, oldest first. */ + pending: ServerErrorInput[]; + /** Stops the console capture `captureSsrConsoleErrors` started. */ + stopConsole?: () => void; +} + +/** Errors kept until the devframe server takes them. */ +const MAX_PENDING = 20; + +export function serverErrorsRegistry(): ServerErrorsRegistry { + const g = globalThis as { __PANGULAR_SERVER_ERRORS__?: ServerErrorsRegistry }; + return (g.__PANGULAR_SERVER_ERRORS__ ??= { pending: [] }); +} + +/** Hands an error to the devframe server, or keeps it until one starts. */ +export function recordServerError(error: ServerErrorInput): void { + const registry = serverErrorsRegistry(); + const stamped = { ...error, at: error.at ?? Date.now() }; + if (registry.record) { + try { + registry.record(stamped); + } catch { + // The devtools never break the dev server. + } + return; + } + registry.pending.push(stamped); + if (registry.pending.length > MAX_PENDING) registry.pending.shift(); +} + +/** Starts handing errors to `record`, the kept ones first. Returns the stop function. */ +export function takeServerErrors(record: (error: ServerErrorInput) => void): () => void { + const registry = serverErrorsRegistry(); + registry.record = record; + for (const error of registry.pending.splice(0)) record(error); + return () => { + if (registry.record === record) registry.record = undefined; + }; +} + +function textOf(value: unknown): string { + try { + if (value instanceof Error) return value.message || value.name; + return typeof value === 'string' ? value : String(value); + } catch { + return ''; + } +} + +/** + * Turns the arguments of a server `console.error` into an SSR error, or + * `undefined` when it is not one. A call counts when it runs inside a server + * render a tracer marked (`ssr-context.ts`), or when it is Angular's + * ErrorHandler format, `console.error('ERROR', error)`, which it uses on + * every platform. + */ +export function ssrErrorFromConsole(args: readonly unknown[]): ServerErrorInput | undefined { + if (!args.length) return undefined; + const context = ssrRegistry().context?.getStore(); + const requestId = context?.requestId; + const active = requestId ? ssrRegistry().active.get(requestId) : undefined; + const angular = args[0] === 'ERROR' && args.length > 1; + if (!active && !angular) return undefined; + const parts = angular ? args.slice(1) : args; + const error = parts.find((part) => part instanceof Error) as Error | undefined; + const message = parts.map(textOf).filter(Boolean).join(' ').trim(); + if (!message) return undefined; + return { + kind: 'ssr', + message, + ...(error?.stack ? { stack: error.stack } : {}), + ...(active?.url ? { url: active.url } : {}), + ...(active && requestId ? { requestId } : {}), + }; +} + +/** + * Listens to the server's `console.error` for SSR errors, through the shared + * console tap, so the calls still print as before. Safe to call more than + * once; the returned function stops it. + */ +export function captureSsrConsoleErrors(): () => void { + const registry = serverErrorsRegistry(); + if (registry.stopConsole) return registry.stopConsole; + const stop = tapConsole(['error'], (_level, args) => { + const error = ssrErrorFromConsole(args); + if (error) recordServerError(error); + }); + registry.stopConsole = () => { + stop(); + registry.stopConsole = undefined; + }; + return registry.stopConsole; +} + +/** The `err` of a Vite `{ type: 'error' }` payload, as `ErrorPayload` in `vite/types/hmrPayload.d.ts`. */ +interface ViteErrorBody { + message?: unknown; + stack?: unknown; + id?: unknown; + frame?: unknown; + plugin?: unknown; + loc?: { file?: unknown; line?: unknown; column?: unknown }; +} + +const str = (value: unknown, max: number) => + typeof value === 'string' && value ? value.slice(0, max) : undefined; +const int = (value: unknown) => + typeof value === 'number' && Number.isFinite(value) ? Math.floor(value) : undefined; + +/** A Vite error payload as a server error, or `undefined` for any other payload. */ +export function viteErrorFromPayload(payload: unknown): ServerErrorInput | undefined { + if (!payload || typeof payload !== 'object') return undefined; + const { type, err } = payload as { type?: unknown; err?: ViteErrorBody }; + if (type !== 'error' || !err || typeof err !== 'object') return undefined; + const message = str(err.message, 5000); + if (!message) return undefined; + const file = str(err.loc?.file, 2000) ?? str(err.id, 2000); + const line = int(err.loc?.line); + const column = int(err.loc?.column); + return { + kind: 'vite', + message, + ...(str(err.stack, 20_000) ? { stack: str(err.stack, 20_000) } : {}), + ...(str(err.plugin, 200) ? { plugin: str(err.plugin, 200) } : {}), + ...(file ? { file: file.replace(/\?.*$/, '') } : {}), + ...(line !== undefined ? { line } : {}), + ...(column !== undefined ? { column } : {}), + ...(str(err.frame, 5000) ? { frame: str(err.frame, 5000) } : {}), + }; +} + +/** The part of Vite's dev server the error capture uses. */ +export interface ViteErrorSource { + ws?: { send: (...args: unknown[]) => unknown }; +} + +/** + * Records the errors Vite sends to its browser overlay: transform, compile + * and SSR module errors. It wraps `server.ws.send` and passes every payload + * on as before. The returned function puts `send` back. + */ +export function watchViteErrors(server: ViteErrorSource): () => void { + const ws = server.ws; + if (!ws || typeof ws.send !== 'function') return () => {}; + const send = ws.send; + const watched = function (this: unknown, ...args: unknown[]) { + try { + const error = viteErrorFromPayload(args[0]); + if (error) recordServerError(error); + } catch { + // The payload still goes out. + } + return send.apply(this, args); + }; + ws.send = watched; + return () => { + if (ws.send === watched) ws.send = send; + }; +} diff --git a/packages/devtools/src/vite.ts b/packages/devtools/src/vite.ts index 81c25890..3b69102d 100644 --- a/packages/devtools/src/vite.ts +++ b/packages/devtools/src/vite.ts @@ -28,6 +28,7 @@ import { pickPangularConfig, resolvePangularConfig, type PangularConfig } from ' import { overlayTags } from './vite-overlay.ts'; import { signalPrimitivesAlias } from './vite-signals-stub.ts'; import { terminalsEnabled } from './hub-terminals.ts'; +import { watchViteErrors, type ViteErrorSource } from './server-errors.ts'; export type { PangularConfig } from './config.ts'; import type { PangularServerPlugin } from './plugin.ts'; @@ -132,7 +133,8 @@ export function devtoolsUrl(local: string | undefined, base: string): string | u export default function pangularVite(options: PangularViteOptions = {}): Plugin { const base = normalizeHubBase(options.base ?? PANGULAR_HUB_BASE); const { config } = pickPangularConfig(options); - const analog = resolvePangularConfig(config).inspectors.analog; + const inspectors = resolvePangularConfig(config).inspectors; + const analog = inspectors.analog; return { name: 'pangular', apply: 'serve', @@ -198,7 +200,12 @@ export default function pangularVite(options: PangularViteOptions = {}): Plugin if (url) logger.info(` \u279C Pangular Inspector: ${url}`); }; } + // Transform and compile errors Vite shows in its overlay, for the Errors tab. + const stopViteErrors = inspectors.errors + ? watchViteErrors(server as unknown as ViteErrorSource) + : () => {}; httpServer?.once('close', () => { + stopViteErrors(); void devtools.context .then(releaseServerState, () => undefined) .finally(() => devtools.close()); From abe21b91f1e3cef9877606d3f6b83c118551162e Mon Sep 17 00:00:00 2001 From: erKam Date: Sat, 7 Nov 2026 18:49:25 +0000 Subject: [PATCH 06/32] feat: add the Performance and Build inspectors Performance: perf-collector.ts observes LCP, INP, CLS, FCP, TTFB and long tasks (long animation frames where the browser has them) through PerformanceObserver, outside the Angular zone, and names the element and component behind LCP and INP. The page pushes on change with its pageId; the server rates each value, keeps one entry per page and expires it. The web-vitals tool and a Performance tab show it. Build: build-graph.ts reads the Vite dev server's client module graph on request (handed over by the Vite plugin) with its lazy boundaries, and the chunks of the last production build with gzip sizes and, with stats.json, their entry points. The module-graph and chunk-sizes tools and a Build tab show it. Both are new inspectors that inspectors.performance and inspectors.build turn off. Refs #372, #373 --- .../__tests__/performance-build-pages.test.ts | 175 +++++++ app/src/app.ts | 14 + app/src/devtools-config.ts | 2 + app/src/pages/build-inspector.ts | 404 ++++++++++++++++ app/src/pages/performance-inspector.ts | 451 ++++++++++++++++++ app/src/pages/tab-icon.ts | 9 + app/src/types/tab.types.ts | 4 +- apps/docs/src/content/agents/tools.md | 23 +- .../content/getting-started/configuration.md | 2 + apps/docs/src/content/inspectors/build.md | 50 ++ .../src/content/inspectors/performance.md | 67 +++ apps/docs/src/ngmd.config.ts | 2 + packages/devtools/CHANGELOG.md | 4 +- .../src/__tests__/build-graph.test.ts | 227 +++++++++ .../fixtures/agent-metadata-derived.json | 14 +- .../src/__tests__/perf-collector.test.ts | 233 +++++++++ .../src/__tests__/perf-server.test.ts | 128 +++++ .../src/__tests__/vite-errors.test.ts | 11 + packages/devtools/src/build-graph.ts | 343 +++++++++++++ packages/devtools/src/config.ts | 15 + packages/devtools/src/devframe.ts | 15 + packages/devtools/src/overlay.ts | 6 + packages/devtools/src/perf-collector.ts | 395 +++++++++++++++ packages/devtools/src/rpc/build-register.ts | 169 +++++++ packages/devtools/src/rpc/perf-register.ts | 361 ++++++++++++++ packages/devtools/src/vite.ts | 7 + 26 files changed, 3125 insertions(+), 6 deletions(-) create mode 100644 app/src/__tests__/performance-build-pages.test.ts create mode 100644 app/src/pages/build-inspector.ts create mode 100644 app/src/pages/performance-inspector.ts create mode 100644 apps/docs/src/content/inspectors/build.md create mode 100644 apps/docs/src/content/inspectors/performance.md create mode 100644 packages/devtools/src/__tests__/build-graph.test.ts create mode 100644 packages/devtools/src/__tests__/perf-collector.test.ts create mode 100644 packages/devtools/src/__tests__/perf-server.test.ts create mode 100644 packages/devtools/src/build-graph.ts create mode 100644 packages/devtools/src/perf-collector.ts create mode 100644 packages/devtools/src/rpc/build-register.ts create mode 100644 packages/devtools/src/rpc/perf-register.ts diff --git a/app/src/__tests__/performance-build-pages.test.ts b/app/src/__tests__/performance-build-pages.test.ts new file mode 100644 index 00000000..ea5d4a32 --- /dev/null +++ b/app/src/__tests__/performance-build-pages.test.ts @@ -0,0 +1,175 @@ +import { TestBed, type ComponentFixture } from '@angular/core/testing'; +import type { DevframeRpcClient } from 'devframe/client'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { + PerformanceInspector, + formatPerf, + perfPages, + type PerfPage, +} from '../pages/performance-inspector'; +import { BuildInspector, formatBytes, type BuildGraph } from '../pages/build-inspector'; + +function client(options: { state?: unknown; call?: (name: string) => unknown }) { + const call = vi.fn(async (name: string) => options.call?.(name)); + const scoped = { + rpc: { + call, + sharedState: async () => ({ value: () => options.state ?? null, on: () => () => {} }), + }, + }; + return { rpc: { scope: () => scoped } as unknown as DevframeRpcClient, call }; +} + +const page = (extra: Partial = {}): PerfPage => ({ + pageId: 'tab1', + url: '/trips', + title: 'Trips', + reportedAt: 1000, + metrics: { + LCP: { + value: 3100, + rating: 'needs-improvement', + target: 'img#hero', + owner: { id: 'c1-2', name: 'TripCard' }, + }, + CLS: { value: 0.02, rating: 'good' }, + }, + interactions: [{ event: 'click', durationMs: 240, at: 5000, target: 'button#book' }], + longTasks: [ + { at: 100, durationMs: 120, kind: 'longtask' }, + { at: 900, durationMs: 80, kind: 'long-animation-frame', source: 'main.js' }, + ], + longTaskCount: 2, + totalBlockingMs: 100, + supported: ['largest-contentful-paint', 'layout-shift', 'longtask'], + ...extra, +}); + +async function settle(fixture: ComponentFixture) { + await fixture.whenStable(); + await new Promise((resolve) => setTimeout(resolve, 0)); + await fixture.whenStable(); +} + +describe('Performance tab', () => { + let fixture: ComponentFixture; + afterEach(() => { + fixture?.destroy(); + TestBed.resetTestingModule(); + }); + + async function render(state: unknown) { + fixture = TestBed.createComponent(PerformanceInspector); + fixture.componentRef.setInput('rpc', client({ state }).rpc); + document.body.append(fixture.nativeElement); + await settle(fixture as ComponentFixture); + return fixture.nativeElement as HTMLElement; + } + + it('says when no page reports', async () => { + const host = await render({ pages: {} }); + expect(host.textContent).toContain('No page reports performance yet.'); + }); + + it('shows each vital with its rating, owner and why a metric is missing', async () => { + const shown: unknown[] = []; + const host = await render({ pages: { tab1: page() } }); + fixture.componentInstance.showComponent.subscribe((focus) => shown.push(focus)); + const cards = [...host.querySelectorAll('.metric')]; + const card = (name: string) => + cards.find((c) => c.querySelector('abbr')!.textContent === name)!; + expect(card('LCP').dataset['rating']).toBe('needs-improvement'); + expect(card('LCP').textContent).toContain('3.10 s'); + expect(card('LCP').textContent).toContain('Needs improvement'); + expect(card('INP').textContent).toContain('Not measured by this browser'); + expect(card('TTFB').textContent).toContain('Not measured yet'); + card('LCP').querySelector('button.link')!.click(); + expect(shown).toEqual([{ id: 'c1-2', pageId: 'tab1' }]); + expect(host.textContent).toContain('2 long tasks, total blocking time 100 ms.'); + // Newest long task first. + expect(host.querySelectorAll('section')[2]!.querySelector('tbody tr')!.textContent).toContain( + 'main.js', + ); + }); + + it('reads only well-formed pages, newest first, and formats values', () => { + expect( + perfPages({ + pages: { a: page({ reportedAt: 1 }), b: page({ pageId: 'b', reportedAt: 2 }), c: {} }, + }).map((p) => p.pageId), + ).toEqual(['b', 'tab1']); + expect(formatPerf('CLS', 0.1234)).toBe('0.123'); + expect(formatPerf('INP', 240.4)).toBe('240 ms'); + }); +}); + +describe('Build tab', () => { + let fixture: ComponentFixture; + afterEach(() => { + fixture?.destroy(); + TestBed.resetTestingModule(); + }); + + const graph: BuildGraph = { + vite: { + modules: Array.from({ length: 30 }, (_, i) => ({ + url: `/src/m${i}.ts`, + file: `src/m${i}.ts`, + type: 'js' as const, + size: 1000 - i, + importers: 1, + imports: 0, + dynamicImports: [], + })), + totalSize: 30_000, + entries: ['/src/main.ts'], + initialSize: 5000, + lazy: [ + { + url: '/src/trips.ts', + file: 'src/trips.ts', + importers: ['src/app.routes.ts'], + modules: 3, + size: 4200, + }, + ], + }, + outputNote: 'No production build found.', + }; + + async function render(answer: unknown) { + const fake = client({ call: () => answer }); + fixture = TestBed.createComponent(BuildInspector); + fixture.componentRef.setInput('rpc', fake.rpc); + document.body.append(fixture.nativeElement); + await settle(fixture as ComponentFixture); + return { host: fixture.nativeElement as HTMLElement, call: fake.call }; + } + + it('shows lazy boundaries, a capped module list, a filter and the build note', async () => { + const { host, call } = await render(graph); + expect(call).toHaveBeenCalledWith('get-build-graph'); + expect(host.textContent).toContain('src/trips.ts'); + expect(host.textContent).toContain('4.2 kB'); + const rows = () => + host + .querySelectorAll('section')[0]! + .querySelectorAll('table')[1]! + .querySelectorAll('tbody tr'); + expect(rows()).toHaveLength(25); + [...host.querySelectorAll('button')].find((b) => b.textContent!.includes('Show all'))!.click(); + await fixture.whenStable(); + expect(rows()).toHaveLength(30); + fixture.componentInstance.filter.set('m29'); + await fixture.whenStable(); + expect(rows()).toHaveLength(1); + fixture.componentInstance.filter.set('nothing'); + await fixture.whenStable(); + expect(host.textContent).toContain('No module matches'); + expect(host.textContent).toContain('No production build found.'); + }); + + it('formats bytes', () => { + expect(formatBytes(4200)).toBe('4.2 kB'); + }); +}); diff --git a/app/src/app.ts b/app/src/app.ts index b4cb2f1c..051aca67 100644 --- a/app/src/app.ts +++ b/app/src/app.ts @@ -32,6 +32,8 @@ import { NetworkInspector } from './pages/network-inspector'; import { ComingSoon, type ComingSoonInfo } from './pages/coming-soon'; import { CapacitorInspector } from './pages/capacitor-inspector'; import { ErrorsInspector } from './pages/errors-inspector'; +import { PerformanceInspector } from './pages/performance-inspector'; +import { BuildInspector } from './pages/build-inspector'; import { PluginsInspector, pluginsReporting } from './pages/plugins-inspector'; import { AuditInspector } from './pages/audit-inspector'; import { Timeline } from './pages/timeline'; @@ -218,6 +220,8 @@ function readView(): View | null { NetworkInspector, CapacitorInspector, ErrorsInspector, + PerformanceInspector, + BuildInspector, PluginsInspector, AuditInspector, Timeline, @@ -593,6 +597,16 @@ function readView(): View | null { @case ('plugins') { } + @case ('performance') { + + } + @case ('build') { + + } @case ('errors') { > = { capacitor: 'capacitor', errors: 'errors', plugins: 'plugins', + performance: 'performance', + build: 'build', }; /** The `inspectors` key that turns a tab on or off, if any. */ diff --git a/app/src/pages/build-inspector.ts b/app/src/pages/build-inspector.ts new file mode 100644 index 00000000..d81353cf --- /dev/null +++ b/app/src/pages/build-inspector.ts @@ -0,0 +1,404 @@ +import { Component, computed, effect, input, signal } from '@angular/core'; +import type { DevframeRpcClient } from 'devframe/client'; + +/** One module of the Vite dev server's graph, as `build-graph.ts` sends it. */ +export interface GraphModule { + url: string; + file?: string; + type: 'js' | 'css' | 'asset'; + size: number; + importers: number; + imports: number; + dynamicImports: string[]; +} + +export interface LazyBoundary { + url: string; + file?: string; + importers: string[]; + modules: number; + size: number; +} + +export interface BuildChunk { + file: string; + bytes: number; + gzip: number; + initial: boolean; + type: 'js' | 'css'; + entryPoint?: string; + inputs?: { file: string; bytes: number }[]; +} + +export interface BuildGraph { + vite?: { + modules: GraphModule[]; + totalSize: number; + entries: string[]; + initialSize: number; + lazy: LazyBoundary[]; + }; + viteNote?: string; + output?: { + dir: string; + builtAt: number; + chunks: BuildChunk[]; + initialBytes: number; + initialGzip: number; + lazyBytes: number; + stats: boolean; + }; + outputNote?: string; +} + +/** `1234` as `1.2 kB`. */ +export function formatBytes(bytes: number): string { + if (bytes < 1000) return `${bytes} B`; + if (bytes < 1_000_000) return `${(bytes / 1000).toFixed(1)} kB`; + return `${(bytes / 1_000_000).toFixed(2)} MB`; +} + +/** Rows shown per table before **Show all**. */ +export const BUILD_ROWS = 25; + +@Component({ + selector: 'app-build-inspector', + template: ` +

+ What the app loads: the Vite dev server's module graph with its lazy boundaries, and the + chunks of the last production build on disk. +

+
+ +

{{ status() }}

+
+ @if (failed()) { + + } @else if (graph(); as graph) { +
+

Module graph

+ @if (graph.vite; as vite) { +

+ {{ vite.modules.length }} modules, {{ bytes(vite.totalSize) }} transformed (unminified, + only what the browser requested). {{ bytes(vite.initialSize) }} load without + import() from {{ vite.entries.length }} + {{ vite.entries.length === 1 ? 'entry' : 'entries' }}. +

+

Lazy boundaries

+ @if (!vite.lazy.length) { +

+ No module is loaded with import() yet. Visit a lazy route to see it here. +

+ } @else { +
+ + + + + + + + + + + + @for (row of vite.lazy; track row.url) { + + + + + + + } + +
+ Modules loaded with import(), biggest first +
ModuleImported byOwn modulesOwn size
+ {{ row.file ?? row.url }} + + {{ row.importers.join(', ') }} + {{ row.modules }}{{ bytes(row.size) }}
+
+ } +

Modules

+
+ + {{ shownModules().length }} of {{ vite.modules.length }} +
+ @if (!shownModules().length) { +
+

No module matches “{{ filter() }}”.

+ +
+ } @else { +
+ + + + + + + + + + + + @for (row of visibleModules(); track row.url) { + + + + + + + } + +
+ Modules, biggest first +
ModuleSizeImportersImports
+ {{ row.file ?? row.url }} + {{ bytes(row.size) }}{{ row.importers }}{{ row.imports }}
+
+ @if (shownModules().length > visibleModules().length) { + + } + } + } @else { +

{{ graph.viteNote }}

+ } +
+ +
+

Chunks

+ @if (graph.output; as output) { +

+ {{ output.dir }}: initial {{ bytes(output.initialBytes) }} ({{ bytes(output.initialGzip) }} gzip), lazy + {{ bytes(output.lazyBytes) }}. Initial chunks are the ones index.html loads + up front. +

+ @if (!output.stats) { +

+ Build with ng build --stats-json to see the source each chunk starts + from. +

+ } +
+ + + + + + + + + + + + + @for (chunk of output.chunks; track chunk.file) { + + + + + + + + } + +
+ Chunks of the production build, initial first +
ChunkLoadsSizeGzipEntry point
+ {{ chunk.file }} + {{ chunk.initial ? 'Initial' : 'Lazy' }}{{ bytes(chunk.bytes) }}{{ bytes(chunk.gzip) }} + {{ chunk.entryPoint ?? '' }} +
+
+ } @else { +

{{ graph.outputNote }}

+ } +
+ } @else { +

Reading the build…

+ } + `, + styles: ` + @use 'mixins' as m; + :host { + display: block; + font-size: 13px; + } + .intro, + .summary { + margin: 0 0 12px; + color: var(--text-2); + line-height: 1.5; + } + .toolbar { + display: flex; + gap: 8px; + align-items: center; + margin: 0 0 12px; + } + h2 { + margin: 16px 0 8px; + color: var(--text-strong); + font-size: 14px; + } + h3 { + margin: 12px 0 6px; + color: var(--text-strong); + font-size: 13px; + } + code { + font-family: var(--font-mono); + font-size: 12px; + overflow-wrap: anywhere; + } + .filter { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + margin: 0 0 8px; + } + .filter input { + flex: 1 1 200px; + min-width: 0; + height: var(--control-h); + padding: 0 10px; + border: 1px solid var(--control-border); + border-radius: var(--radius-sm); + background: var(--surface); + color: var(--text); + font: inherit; + } + .filter input:focus-visible { + @include m.focus-ring; + } + .count { + color: var(--text-2); + font-size: 12px; + } + .scroll { + max-width: 100%; + overflow-x: auto; + } + table { + width: 100%; + border-collapse: collapse; + } + th, + td { + padding: 6px 8px; + border-bottom: 1px solid var(--border); + text-align: left; + vertical-align: top; + } + th { + color: var(--text-2); + font-weight: 600; + } + .num { + text-align: right; + font-variant-numeric: tabular-nums; + white-space: nowrap; + } + .notice { + margin: 0 0 8px; + padding: 8px 12px; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: var(--surface); + color: var(--text-2); + } + .empty, + .state { + margin: 0 0 12px; + color: var(--text-2); + } + .empty p, + .state p { + margin: 0 0 8px; + } + .small { + height: var(--control-h); + padding: 0 12px; + border: 1px solid var(--control-border); + border-radius: var(--radius-sm); + background: var(--surface-2); + color: var(--text); + } + .small:focus-visible { + @include m.focus-ring; + } + .more { + margin-top: 8px; + } + `, +}) +export class BuildInspector { + readonly rpc = input(null); + + protected readonly bytes = formatBytes; + + readonly graph = signal(null); + readonly loading = signal(false); + readonly failed = signal(false); + readonly status = signal(''); + readonly filter = signal(''); + readonly allModules = signal(false); + + readonly shownModules = computed(() => { + const modules = this.graph()?.vite?.modules ?? []; + const text = this.filter().trim().toLowerCase(); + return text + ? modules.filter((mod) => `${mod.url} ${mod.file ?? ''}`.toLowerCase().includes(text)) + : modules; + }); + readonly visibleModules = computed(() => + this.allModules() ? this.shownModules() : this.shownModules().slice(0, BUILD_ROWS), + ); + + constructor() { + effect(() => { + const client = this.rpc(); + if (client) void this.load(client); + }); + } + + refresh() { + const client = this.rpc(); + if (client) void this.load(client, true); + } + + private async load(client: DevframeRpcClient, announce = false) { + this.loading.set(true); + this.failed.set(false); + try { + const graph = (await client.scope('pangular').rpc.call('get-build-graph')) as BuildGraph; + this.graph.set(graph && typeof graph === 'object' ? graph : {}); + if (announce) this.status.set('Build data refreshed.'); + } catch { + this.failed.set(true); + } finally { + this.loading.set(false); + } + } +} diff --git a/app/src/pages/performance-inspector.ts b/app/src/pages/performance-inspector.ts new file mode 100644 index 00000000..1a05eeb4 --- /dev/null +++ b/app/src/pages/performance-inspector.ts @@ -0,0 +1,451 @@ +import { + Component, + DestroyRef, + computed, + effect, + inject, + input, + linkedSignal, + output, + signal, +} from '@angular/core'; +import type { DevframeRpcClient } from 'devframe/client'; +import { hostPageId } from '../page-id'; +import { Select, type SelectOption } from '../ui/select'; +import type { ComponentFocus } from '../types/component-focus.types'; + +export type PerfMetricName = 'LCP' | 'CLS' | 'INP' | 'FCP' | 'TTFB'; +export type PerfRating = 'good' | 'needs-improvement' | 'poor'; + +interface PerfOwner { + id: string; + name: string; +} + +export interface PerfMetric { + value: number; + rating: PerfRating; + target?: string; + owner?: PerfOwner; + event?: string; + at?: number; +} + +export interface PerfPage { + pageId: string; + url?: string; + title?: string; + reportedAt: number; + metrics: Partial>; + interactions: { + event: string; + durationMs: number; + at: number; + target?: string; + owner?: PerfOwner; + }[]; + longTasks: { at: number; durationMs: number; kind: string; source?: string }[]; + longTaskCount: number; + totalBlockingMs: number; + supported: string[]; +} + +export const PERF_METRICS: readonly { name: PerfMetricName; label: string; entry: string }[] = [ + { name: 'LCP', label: 'Largest Contentful Paint', entry: 'largest-contentful-paint' }, + { name: 'INP', label: 'Interaction to Next Paint', entry: 'event' }, + { name: 'CLS', label: 'Cumulative Layout Shift', entry: 'layout-shift' }, + { name: 'FCP', label: 'First Contentful Paint', entry: 'paint' }, + { name: 'TTFB', label: 'Time to First Byte', entry: 'navigation' }, +]; + +const RATING_LABEL: Record = { + good: 'Good', + 'needs-improvement': 'Needs improvement', + poor: 'Poor', +}; + +/** The pages of the `performance` shared state, newest first; bad entries are dropped. */ +export function perfPages(value: unknown): PerfPage[] { + const pages = (value as { pages?: Record } | null)?.pages; + if (!pages || typeof pages !== 'object') return []; + return Object.values(pages) + .filter( + (page): page is PerfPage => + !!page && + typeof page === 'object' && + typeof (page as PerfPage).pageId === 'string' && + !!(page as PerfPage).metrics && + Array.isArray((page as PerfPage).longTasks), + ) + .sort((a, b) => b.reportedAt - a.reportedAt); +} + +export function formatPerf(name: PerfMetricName, value: number): string { + if (name === 'CLS') return value.toFixed(3); + return formatMs(value); +} + +export function formatMs(value: number): string { + return value >= 1000 ? `${(value / 1000).toFixed(2)} s` : `${Math.round(value)} ms`; +} + +@Component({ + selector: 'app-performance-inspector', + imports: [Select], + template: ` + @if (!loaded()) { +

Loading performance…

+ } @else if (failed()) { + + } @else if (!pages().length) { +
+

No page reports performance yet.

+

+ Open the app with the overlay loaded. The browser measures web vitals as the page loads + and as you use it. +

+
+ } @else { +

+ Web vitals the browser measured since the page loaded, rated by the web.dev thresholds, and + the tasks that blocked the main thread. +

+ @if (pages().length > 1) { +
+ +
+ } + @if (page(); as page) { +
+

Web vitals

+
    + @for (metric of metrics; track metric.name) { +
  • + + {{ metric.name }} + {{ metric.label }} + + @if (page.metrics[metric.name]; as value) { + {{ format(metric.name, value.value) }} + {{ ratingLabel[value.rating] }} + @if (value.event || value.target) { + {{ value.event ? value.event + ' on ' : '' }}{{ value.target }} + } + @if (value.owner; as owner) { + @if (canShowComponent()) { + + } @else { + In {{ owner.name }} + } + } + } @else { + – + {{ missing(page, metric.entry) }} + } +
  • + } +
+
+ +
+

Slowest interactions

+ @if (!page.interactions.length) { +

No interaction of 40 ms or more yet. Click or type in the app.

+ } @else { + + + + + + + + + + + + @for (row of page.interactions; track $index) { + + + + + + + } + +
+ Slowest interactions, slowest first +
EventTargetDurationAt
+ {{ row.event }} + + {{ row.target ?? '' }} + @if (row.owner) { + in {{ row.owner.name }} + } + {{ formatMs(row.durationMs) }}{{ formatMs(row.at) }}
+ } +
+ +
+

Long tasks

+

+ {{ page.longTaskCount }} long {{ page.longTaskCount === 1 ? 'task' : 'tasks' }}, total + blocking time {{ formatMs(page.totalBlockingMs) }}. +

+ @if (page.longTasks.length) { + + + + + + + + + + + @for (task of latestTasks(); track $index) { + + + + + + } + +
+ Latest long tasks, newest first +
AtDurationSource
{{ formatMs(task.at) }}{{ formatMs(task.durationMs) }} + {{ + task.source ?? + (task.kind === 'long-animation-frame' ? 'long animation frame' : 'task') + }} +
+ } +
+ } + } + `, + styles: ` + @use 'mixins' as m; + :host { + display: block; + font-size: 13px; + } + .intro, + .summary { + margin: 0 0 12px; + color: var(--text-2); + line-height: 1.5; + } + .toolbar { + margin: 0 0 12px; + } + h2 { + margin: 16px 0 8px; + color: var(--text-strong); + font-size: 14px; + } + code { + font-family: var(--font-mono); + font-size: 12px; + overflow-wrap: anywhere; + } + .muted { + color: var(--text-2); + } + .metrics { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(min(100%, 200px), 1fr)); + gap: 8px; + margin: 0; + padding: 0; + list-style: none; + } + .metric { + @include m.panel; + display: grid; + gap: 4px; + align-content: start; + min-width: 0; + padding: 10px 12px; + border-left: 3px solid var(--border); + } + .metric[data-rating='good'] { + border-left-color: var(--ok); + } + .metric[data-rating='needs-improvement'] { + border-left-color: var(--warn); + } + .metric[data-rating='poor'] { + border-left-color: var(--danger); + } + .name abbr { + color: var(--text-2); + font-weight: 600; + text-decoration: none; + } + .value { + color: var(--text-strong); + font-size: 20px; + font-weight: 600; + font-variant-numeric: tabular-nums; + } + .rating { + font-size: 12px; + } + [data-rating='good'] .rating { + color: var(--ok-text); + } + [data-rating='needs-improvement'] .rating { + color: var(--warn-text); + } + [data-rating='poor'] .rating { + color: var(--danger-text); + } + .link { + justify-self: start; + padding: 0; + border: 0; + border-radius: 2px; + background: none; + color: var(--accent-text); + font: inherit; + font-size: 12px; + text-align: left; + text-decoration: underline; + text-underline-offset: 2px; + cursor: pointer; + } + .link:focus-visible { + @include m.focus-ring; + } + table { + width: 100%; + border-collapse: collapse; + } + th, + td { + padding: 6px 8px; + border-bottom: 1px solid var(--border); + text-align: left; + vertical-align: top; + } + th { + color: var(--text-2); + font-weight: 600; + } + .num { + text-align: right; + font-variant-numeric: tabular-nums; + white-space: nowrap; + } + .empty, + .state { + margin: 0 0 12px; + color: var(--text-2); + } + .state p { + margin: 0 0 8px; + } + .small { + height: var(--control-h); + padding: 0 12px; + border: 1px solid var(--control-border); + border-radius: var(--radius-sm); + background: var(--surface-2); + color: var(--text); + } + .small:focus-visible { + @include m.focus-ring; + } + `, +}) +export class PerformanceInspector { + readonly rpc = input(null); + /** Whether the Components tab is on, so "Show component" has somewhere to go. */ + readonly canShowComponent = input(true); + readonly showComponent = output(); + + private readonly destroyRef = inject(DestroyRef); + private readonly hostPageId = hostPageId(); + private unsubscribe?: () => void; + + protected readonly metrics = PERF_METRICS; + protected readonly ratingLabel = RATING_LABEL; + protected readonly format = formatPerf; + protected readonly formatMs = formatMs; + + readonly pages = signal([]); + readonly loaded = signal(false); + readonly failed = signal(false); + /** Keeps the picked page while it reports; starts on the panel's own page. */ + readonly pageId = linkedSignal({ + source: () => this.pages(), + computation: (pages, previous) => + previous?.value && pages.some((page) => page.pageId === previous.value) + ? previous.value + : ((pages.find((page) => page.pageId === this.hostPageId) ?? pages[0])?.pageId ?? ''), + }); + readonly page = computed(() => this.pages().find((page) => page.pageId === this.pageId())); + readonly pageOptions = computed(() => + this.pages().map((page) => ({ + value: page.pageId, + label: page.title || page.url || page.pageId, + hint: page.url, + })), + ); + readonly latestTasks = computed(() => [...(this.page()?.longTasks ?? [])].reverse()); + + constructor() { + effect(() => { + const client = this.rpc(); + if (client) void this.load(client); + }); + this.destroyRef.onDestroy(() => this.unsubscribe?.()); + } + + retry() { + const client = this.rpc(); + if (client) void this.load(client); + } + + /** Why a metric has no value yet. */ + missing(page: PerfPage, entry: string): string { + if (entry !== 'navigation' && !page.supported.includes(entry)) { + return 'Not measured by this browser'; + } + return 'Not measured yet'; + } + + private async load(client: DevframeRpcClient) { + this.failed.set(false); + try { + const state = await client.scope('pangular').rpc.sharedState('performance'); + if (this.destroyRef.destroyed) return; + const apply = (value: unknown) => this.pages.set(perfPages(value)); + apply(state.value()); + this.unsubscribe?.(); + this.unsubscribe = state.on('updated', apply); + } catch { + this.failed.set(true); + } finally { + this.loaded.set(true); + } + } +} diff --git a/app/src/pages/tab-icon.ts b/app/src/pages/tab-icon.ts index 194699ff..d2d05051 100644 --- a/app/src/pages/tab-icon.ts +++ b/app/src/pages/tab-icon.ts @@ -92,6 +92,15 @@ import { Component, input } from '@angular/core'; } + @case ('performance') { + + + } + @case ('build') { + + + + } @case ('plugins') { - Ninety-nine tools, grouped by inspector. Each one answers a question you would otherwise answer by clicking through the panel. + One hundred and two tools, grouped by inspector. Each one answers a question you would otherwise answer by clicking through the panel. # Tools @@ -940,6 +940,27 @@ The explainers are the ones the [Errors](../inspectors/errors.md#explainers) tab `code` accepts `NG0100`, `ng100` or `NG01203`. `explain-error` says so when Angular's error reference has no page for a code, and gives no known cause then. Pass the owner's instance id to `inspect-component` for its inputs and state. `list-errors` takes `limit` up to 100 (default 30), and `list-console` up to 200 (default 50). `since` keeps the console calls made since a duration back from now (`30s`, `5m`, `1h`), an ISO date or epoch milliseconds, so you can read what one reproduction logged. A call repeated in a row counts from its last repeat. The page keeps its last 200 calls and sends them while a panel or an agent watches, so an agent call to an idle page gets what it logged meanwhile. Messages, stacks and console text are masked like other live values. +## Performance + +This tool reads the web vitals the page measured. See [Performance](../inspectors/performance.md). Reads: page. + +| Tool | What it answers | Arguments | +| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| `web-vitals` | LCP, INP, CLS, FCP and TTFB with value and web.dev rating, and the element, event and component behind each, then the slowest interactions, the long task count, the total blocking time and the latest long tasks. | none | + +A metric the browser doesn't measure, or has no entry for yet, says `none yet`, and the answer names the entry types the browser lacks. + +## Build + +These tools read what the app loads. See [Build](../inspectors/build.md). + +| Tool | What it answers | Arguments | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `module-graph` | The Vite dev server's module graph: the entries, the lazy boundaries (`import()`) with the modules and size each pulls in, and the biggest modules. With `search`, matching modules with their importers, imports and `import()` calls. | `search`, `limit` | +| `chunk-sizes` | The chunks of the last production build: initial or lazy, size, gzip size, and with `stats.json` the entry point. | `limit` | + +`module-graph` needs the Vite plugin, in the dev server's process; it reads nothing on the stdio MCP server. Sizes are transformed, unminified code, for modules the browser requested. `chunk-sizes` reads the build output on disk, so it works anywhere the project is, and says how to build when there is none. `limit` defaults to 20 for `module-graph` (at most 200) and 30 for `chunk-sizes` (at most 500). + ## Audit ### audit Action diff --git a/apps/docs/src/content/getting-started/configuration.md b/apps/docs/src/content/getting-started/configuration.md index 2bdaf082..4d01ee5b 100644 --- a/apps/docs/src/content/getting-started/configuration.md +++ b/apps/docs/src/content/getting-started/configuration.md @@ -144,6 +144,8 @@ interface PangularConfig { | `agent.tools.` | `true` | `false` hides one inspector's agent tools and resources, and its [shared-state keys](../agents/resources.md#shared-state) over HTTP MCP. The tab stays. | | `agent.browserTools` | `false` | `true` registers the `read` agent tools on the page for [browser agents](../agents/browser-agents.md) (WebMCP and Chrome DevTools for agents). Off by default, because any script on the development page could then call them. This is where the **Browser agent tools** switch in [Settings](./settings.md#browser-agents) starts. The switch changes it for every page until the dev server restarts. | +The inspectors are `components`, `injectors`, `signals`, `ngrx`, `tanstackQuery`, `forms`, `router`, `pipes`, `http`, `analog`, `capacitor`, `errors`, `plugins`, `performance` ([Performance](../inspectors/performance.md)) and `build` ([Build](../inspectors/build.md)). + `agent.tools` has the same keys as `inspectors`. An inspector that is off has no agent tools, whatever `agent.tools` says. If you open a tab that is turned off, the panel says so, shows the option that turns it back on, such as `inspectors: {ngrx: true}`, and links to this section. With `inspectors.components` set to `false`, the [Chrome extension](./chrome-extension.md) does not follow the **Elements** panel. diff --git a/apps/docs/src/content/inspectors/build.md b/apps/docs/src/content/inspectors/build.md new file mode 100644 index 00000000..bfbe16cd --- /dev/null +++ b/apps/docs/src/content/inspectors/build.md @@ -0,0 +1,50 @@ +--- +title: Build +description: The Vite dev server's module graph with its lazy boundaries, and the chunk sizes of the last production build. +--- + + + What the app loads: the modules Vite serves, what each lazy route pulls in, and how big the production chunks are. + + +# Build + +The Build tab answers what the app loads and how much of it. The **Module graph** comes from the Vite dev server that serves the app. **Chunks** come from the last production build on disk. + +## What it shows + +### Module graph + +The modules the Vite dev server served, with their transformed size (unminified, and only for modules the browser requested), how many modules import each one and how many it imports. The summary gives the total and the size that loads from the entries, such as `main.ts`, without `import()`. + +**Lazy boundaries** lists each module loaded with `import()`, such as a route's `loadComponent`, with who imports it and the modules and size it pulls in that the entries don't already load. A boundary shows up once the browser loaded it, so visit a lazy route first. The module list starts with the 25 biggest; **Show all** lists every module, and the filter keeps modules whose file or URL contains the text. + +### Chunks + +The JavaScript and CSS files of the browser build, initial ones first: **Initial** for what `index.html` loads up front (a script, a module preload or a stylesheet), **Lazy** for the rest, with the size on disk and the gzip size. The summary adds them up. Build with `ng build --stats-json` and each chunk also shows its entry point, the source file it starts from, which names the route behind a lazy chunk. + +The tab looks for the build in the `outputPath` of the first application in `angular.json` (its `browser` folder), then `dist/browser`, then `dist`. **Refresh** reads both again, for example after a rebuild. + +## Where the data comes from + +The [Vite plugin](../getting-started/vite.md) hands the server Vite's client module graph. Nothing is read until the tab or an agent asks, and reading it changes nothing in Vite. Without the Vite plugin, the module graph says so, and the chunks still work. Module URLs are masked like other URLs. + +## Agent tools + +| Tool | What it does | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| `pangular:module-graph` | The lazy boundaries and the biggest modules. `search` finds modules with their importers, imports and `import()` calls. | +| `pangular:chunk-sizes` | The chunks of the last production build, initial or lazy, with sizes and entry points. | + +See [Tools](../agents/tools.md#build). + +## Limits and gotchas + +- Dev sizes are transformed, unminified code, so they are larger than production chunks. Use them to compare modules, and **Chunks** for real sizes. +- The module graph has no transform timings: Vite does not record them. +- `inspectors: {build: false}` removes the tab and both tools. + +## Where to next + +- [Performance](./performance.md): how the loaded page performs in the browser. +- [Vite plugin](../getting-started/vite.md): set up the plugin the module graph needs. diff --git a/apps/docs/src/content/inspectors/performance.md b/apps/docs/src/content/inspectors/performance.md new file mode 100644 index 00000000..6e955279 --- /dev/null +++ b/apps/docs/src/content/inspectors/performance.md @@ -0,0 +1,67 @@ +--- +title: Performance +description: Web vitals and long tasks of the running page, as the browser measured them, with the element and component behind each one. +--- + + + How fast the running page feels: Largest Contentful Paint, Interaction to Next Paint, Cumulative Layout Shift and the tasks that blocked the main thread, tied to the components on the page. + + +# Performance + +The Performance tab shows the web vitals the browser measured for the running page since it loaded, rated by the [web.dev](https://web.dev/articles/vitals) thresholds, and the long tasks that blocked the main thread. When a metric has an element behind it, the tab names the element and the component that owns it. + +## What it shows + +### Web vitals + +One card per metric, with its value and rating (**Good**, **Needs improvement** or **Poor**): + +| Metric | Good at or under | Poor over | Where it comes from | +| ---------------------------------- | ---------------- | --------- | --------------------------------------------------------------------------------- | +| **LCP**, Largest Contentful Paint | 2.5 s | 4 s | The last `largest-contentful-paint` entry, with its element and component. | +| **INP**, Interaction to Next Paint | 200 ms | 500 ms | The slowest interaction (98th percentile past 50), with its event and target. | +| **CLS**, Cumulative Layout Shift | 0.1 | 0.25 | The largest session window of `layout-shift` entries, without shifts after input. | +| **FCP**, First Contentful Paint | 1.8 s | 3 s | The `first-contentful-paint` paint entry. | +| **TTFB**, Time to First Byte | 800 ms | 1.8 s | `responseStart` of the navigation entry. | + +A metric the browser doesn't measure says **Not measured by this browser**. LCP, INP and CLS need a Chromium-based browser. A metric it measures but has no entry for yet says **Not measured yet**, such as INP before the first click. When the element belongs to a component and the Components tab is on, **Show component** selects it there. + +### Slowest interactions + +The ten slowest interactions of 40 ms or more, slowest first: the event, such as `click` or `keydown`, its target and owning component, the duration and when it started. Each interaction counts once, by its slowest event. + +### Long tasks + +How many long tasks the page had and the total blocking time, the sum of each task's time over 50 ms, then the latest 20, newest first. Where the browser reports long animation frames, the tab uses those instead of long tasks and names the script that ran longest in each. + +### Pages + +When several tabs report, **Page** picks one. The panel of a tab starts on that tab. + +## Where the data comes from + +The overlay subscribes to the browser's `PerformanceObserver` with `buffered: true`, so entries from before it loaded count too. It only observes: nothing runs on a timer, and the observers are created outside the Angular zone, so an entry never makes the app tick. The report goes out with the overlay's regular pushes, only when it changed, and the server forgets the page when it closes or stops reporting. Element labels and URLs are masked like other live data. + +## Agent tools + +| Tool | What it does | +| --------------------- | ------------------------------------------------------------------------------------------------------------------- | +| `pangular:web-vitals` | Each metric with its value, rating, element and component, then the slowest interactions and the latest long tasks. | + +See [Tools](../agents/tools.md#performance). + +## Limits and gotchas + +### Values are for this load + +The values cover the page since it loaded. A client navigation keeps them; reload the page to measure a route on its own. Development builds are slower than production ones, so read the ratings as relative, and compare before and after a change. + +### Turn it off + +`inspectors: {performance: false}` removes the tab, the page collector and the tool. See [Configuration](../getting-started/configuration.md#inspectors-and-agent-tools). + +## Where to next + +- [Build](./build.md): the module graph and chunk sizes behind a slow load. +- [Timeline](./timeline.md): change detection, HTTP and router events on one time axis. diff --git a/apps/docs/src/ngmd.config.ts b/apps/docs/src/ngmd.config.ts index 6d4a0a23..79292612 100644 --- a/apps/docs/src/ngmd.config.ts +++ b/apps/docs/src/ngmd.config.ts @@ -210,6 +210,8 @@ const config: NgmdConfig = { {label: 'Analog', href: '/inspectors/analog'}, {label: 'Capacitor', href: '/inspectors/capacitor'}, {label: 'Errors', href: '/inspectors/errors'}, + {label: 'Performance', href: '/inspectors/performance'}, + {label: 'Build', href: '/inspectors/build'}, {label: 'Audit', href: '/inspectors/audit'}, ], }, diff --git a/packages/devtools/CHANGELOG.md b/packages/devtools/CHANGELOG.md index 38fa0c5b..2b2908be 100644 --- a/packages/devtools/CHANGELOG.md +++ b/packages/devtools/CHANGELOG.md @@ -153,13 +153,15 @@ What remains: `/config` exports `DEFAULT_POPUP_HOTKEY`, `PANGULAR_ACTIONS`, `PAN - **Audit:** one ranked list of findings across the inspectors, an axe pass, and before and after sessions. - **Capacitor:** a tab for the platform, plugins, plugin calls and listeners of a Capacitor app. - **Forms, Router, Pipes and Analog:** filters, lint navigation, **Clear filters**, a route count, routed components in the **Components** tab, and search and a failed filter for Analog server calls. +- **Performance:** a tab for LCP, INP, CLS, FCP and TTFB with the element and component behind each, the slowest interactions and long tasks (#372). +- **Build:** a tab for the Vite dev server's module graph with its lazy boundaries, and the chunk sizes of the last production build (#372, #373). - **Plugin API:** a library can add its own tab, agent tools and shared state. - Angular Native and NativeScript pages report change detection, leaks, errors, stores, TanStack Query, provider values, plugins and startup timing too (#407). - Angular features are detected at runtime, and the panel says what an older version lacks. #### Agents and MCP -- 99 agent tools. New tools include `audit`, `snapshot`, `wait-for`, `scan-templates` (#415), `list-errors`, `explain-error`, `list-console`, `list-server-errors`, `find-leaks`, `diagnose-change-detection` (#395), `set-component-property`, `set-signal`, `track-subscriptions`, `watch-signal`, `hydration`, `screenshot-component`, `open-in-panel`, `startup-timing`, `read-provider-value`, `record-change-detection`, `probe-route`, `record-guards`, the TanStack Query, Capacitor, plugin and NgRx session tools, and tools for HTTP fault rules and SSR overrides. +- 102 agent tools. New tools include `audit`, `snapshot`, `wait-for`, `scan-templates` (#415), `list-errors`, `explain-error`, `list-console`, `list-server-errors`, `web-vitals`, `module-graph`, `chunk-sizes`, `find-leaks`, `diagnose-change-detection` (#395), `set-component-property`, `set-signal`, `track-subscriptions`, `watch-signal`, `hydration`, `screenshot-component`, `open-in-panel`, `startup-timing`, `read-provider-value`, `record-change-detection`, `probe-route`, `record-guards`, the TanStack Query, Capacitor, plugin and NgRx session tools, and tools for HTTP fault rules and SSR overrides. - One naming scheme for tools and arguments, and plain markdown answers. See **Agent tools renamed**. - Tool titles, inspector tags and safety tiers (`action` and `destructive`). - Every answer names the page it read, and one rule picks the default page for every tool. diff --git a/packages/devtools/src/__tests__/build-graph.test.ts b/packages/devtools/src/__tests__/build-graph.test.ts new file mode 100644 index 00000000..78709a81 --- /dev/null +++ b/packages/devtools/src/__tests__/build-graph.test.ts @@ -0,0 +1,227 @@ +import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { createHostContext } from 'devframe/node'; +import { afterEach, describe, expect, it } from 'vitest'; +import { createPangular } from '../devframe.ts'; +import { + buildRegistry, + findBuildOutput, + initialFiles, + readBuildGraph, + readBuildOutput, + readViteGraph, + viteGraphSource, + type ViteModuleLike, +} from '../build-graph.ts'; +import { chunkSizesText, formatBytes, moduleGraphText } from '../rpc/build-register.ts'; +import { answerText } from './tool-answer.ts'; + +/** A Vite-like module graph: `links` are `[from, to, dynamic?]`. */ +function graph( + root: string, + sizes: Record, + links: [string, string, boolean?][], +): ViteModuleLike[] { + const mods = new Map(); + for (const [url, size] of Object.entries(sizes)) { + mods.set(url, { + url, + file: join(root, url), + type: url.endsWith('.css') ? 'css' : 'js', + importers: new Set(), + importedModules: new Set(), + transformResult: size ? { code: 'x'.repeat(size) } : null, + staticImportedUrls: new Set(), + }); + } + for (const [from, to, dynamic] of links) { + const a = mods.get(from)!; + const b = mods.get(to)!; + a.importedModules.add(b); + b.importers.add(a); + if (!dynamic) a.staticImportedUrls!.add(to); + } + return [...mods.values()]; +} + +function project(files: Record): string { + const dir = mkdtempSync(join(tmpdir(), 'pangular-build-')); + for (const [name, content] of Object.entries(files)) { + mkdirSync(join(dir, name, '..'), { recursive: true }); + writeFileSync(join(dir, name), content); + } + return dir; +} + +afterEach(() => { + delete (globalThis as { __PANGULAR_BUILD__?: unknown }).__PANGULAR_BUILD__; +}); + +describe('readViteGraph', () => { + const root = '/app'; + const modules = graph( + root, + { + '/src/main.ts': 100, + '/src/app.routes.ts': 50, + '/src/shared.ts': 30, + '/src/trips.page.ts': 400, + '/src/trip-map.ts': 600, + '/src/admin.page.ts': 80, + }, + [ + ['/src/main.ts', '/src/app.routes.ts'], + ['/src/app.routes.ts', '/src/shared.ts'], + ['/src/app.routes.ts', '/src/trips.page.ts', true], + ['/src/app.routes.ts', '/src/admin.page.ts', true], + ['/src/trips.page.ts', '/src/trip-map.ts'], + ['/src/trips.page.ts', '/src/shared.ts'], + ], + ); + + it('splits what loads up front from each lazy boundary', () => { + const result = readViteGraph({ root, modules: () => modules }); + expect(result.entries).toEqual(['/src/main.ts']); + expect(result.initialSize).toBe(180); + expect(result.totalSize).toBe(1260); + expect(result.lazy.map((b) => [b.file, b.modules, b.size, b.importers])).toEqual([ + // shared.ts already loads up front, so it doesn't count for the trips page. + ['src/trips.page.ts', 2, 1000, ['src/app.routes.ts']], + ['src/admin.page.ts', 1, 80, ['src/app.routes.ts']], + ]); + const routes = result.modules.find((m) => m.url === '/src/app.routes.ts')!; + expect(routes.dynamicImports.sort()).toEqual(['/src/admin.page.ts', '/src/trips.page.ts']); + expect(result.modules[0]!.url).toBe('/src/trip-map.ts'); + }); + + it('reads the client environment of a Vite 6+ server, and moduleGraph before it', () => { + const map = new Map(modules.map((m) => [m.url, m])); + const v6 = viteGraphSource({ + config: { root }, + environments: { client: { moduleGraph: { idToModuleMap: map } } }, + })!; + expect([...v6.modules()]).toHaveLength(6); + expect(viteGraphSource({ moduleGraph: { idToModuleMap: map } })).toBeDefined(); + expect(viteGraphSource({})).toBeUndefined(); + }); + + it('answers module-graph with lazy boundaries and a search', () => { + buildRegistry().vite = { root, modules: () => modules }; + const read = readBuildGraph('/nowhere'); + const text = moduleGraphText(read); + expect(text).toContain('| `src/trips.page.ts` | `src/app.routes.ts` | 2 | 1.0 kB |'); + const found = moduleGraphText(read, { search: 'routes' }); + expect(found).toContain('Modules matching `routes`: 1.'); + expect(found).toContain('`/src/admin.page.ts`'); + delete buildRegistry().vite; + expect(moduleGraphText(readBuildGraph('/nowhere'))).toMatch(/No Vite dev server/); + }); +}); + +describe('build output', () => { + const html = + ''; + + it('takes scripts, module preloads and stylesheets as initial', () => { + expect([...initialFiles(html)].sort()).toEqual([ + 'chunk-SHARED.js', + 'main-XYZ.js', + 'styles-ABC.css', + ]); + }); + + it('lists chunks with sizes and links them to their source with stats.json', () => { + const root = project({ + 'angular.json': JSON.stringify({ + projects: { + app: { + projectType: 'application', + architect: { build: { options: { outputPath: 'dist/app' } } }, + }, + }, + }), + 'dist/app/browser/index.html': html, + 'dist/app/browser/main-XYZ.js': 'console.log("main");'.repeat(50), + 'dist/app/browser/chunk-SHARED.js': 'export const a = 1;', + 'dist/app/browser/chunk-LAZY.js': 'export const trips = [];'.repeat(20), + 'dist/app/browser/styles-ABC.css': 'body{margin:0}', + 'dist/app/stats.json': JSON.stringify({ + outputs: { + 'chunk-LAZY.js': { + entryPoint: 'src/app/trips/trips.page.ts', + inputs: { 'src/app/trips/trips.page.ts': { bytesInOutput: 300 } }, + }, + }, + }), + }); + const dir = findBuildOutput(root)!; + expect(dir).toBe(join(root, 'dist/app/browser')); + const output = readBuildOutput(root, dir); + expect(output.dir).toBe('dist/app/browser'); + expect(output.stats).toBe(true); + expect(output.chunks.map((c) => [c.file, c.initial])).toEqual([ + ['main-XYZ.js', true], + ['chunk-SHARED.js', true], + ['styles-ABC.css', true], + ['chunk-LAZY.js', false], + ]); + const lazy = output.chunks.find((c) => c.file === 'chunk-LAZY.js')!; + expect(lazy.entryPoint).toBe('src/app/trips/trips.page.ts'); + expect(lazy.gzip).toBeLessThan(lazy.bytes); + expect(output.lazyBytes).toBe(lazy.bytes); + const text = chunkSizesText(readBuildGraph(root)); + expect(text).toContain('| `chunk-LAZY.js` | lazy |'); + expect(text).toContain('`src/app/trips/trips.page.ts`'); + }); + + it('says how to build when there is no output', () => { + const root = project({ 'package.json': '{}' }); + const read = readBuildGraph(root); + expect(read.output).toBeUndefined(); + expect(chunkSizesText(read)).toMatch(/No production build found/); + }); + + it('formats bytes', () => { + expect(formatBytes(999)).toBe('999 B'); + expect(formatBytes(1234)).toBe('1.2 kB'); + expect(formatBytes(2_500_000)).toBe('2.50 MB'); + }); +}); + +describe('build on the devframe server', () => { + async function boot(options = {}) { + const host = { + mountStatic: () => {}, + resolveOrigin: () => 'http://localhost', + getStorageDir: () => '', + }; + const root = project({ 'package.json': '{}' }); + const ctx = await createHostContext({ cwd: root, mode: 'dev', host: host as never }); + await createPangular(options).setup(ctx as never); + return ctx; + } + + it('serves get-build-graph and the two tools, and masks secrets in URLs', async () => { + const modules = graph('/app', { '/src/main.ts?token=supersecretvalue123': 10 }, []); + buildRegistry().vite = { root: '/app', modules: () => modules }; + const ctx = await boot(); + const read = (await (ctx.rpc.invokeLocal as (name: string) => Promise)( + 'pangular:get-build-graph', + )) as { vite: { modules: { url: string }[] }; outputNote: string }; + expect(read.vite.modules[0]!.url).not.toContain('supersecretvalue123'); + expect(read.outputNote).toMatch(/No production build found/); + expect(await answerText(ctx.agent.invoke('pangular:chunk-sizes', {}))).toMatch( + /No production build found/, + ); + const ids = ctx.agent.list().tools.map((t) => t.id); + expect(ids).toEqual(expect.arrayContaining(['pangular:module-graph', 'pangular:chunk-sizes'])); + }); + + it('drops the tools with the build inspector off', async () => { + const ctx = await boot({ inspectors: { build: false } }); + const ids = ctx.agent.list().tools.map((t) => t.id); + expect(ids).not.toContain('pangular:module-graph'); + expect(ids).not.toContain('pangular:chunk-sizes'); + }); +}); diff --git a/packages/devtools/src/__tests__/fixtures/agent-metadata-derived.json b/packages/devtools/src/__tests__/fixtures/agent-metadata-derived.json index 5967e71f..11c018f0 100644 --- a/packages/devtools/src/__tests__/fixtures/agent-metadata-derived.json +++ b/packages/devtools/src/__tests__/fixtures/agent-metadata-derived.json @@ -3,6 +3,7 @@ "capacitor-list-calls": "capacitor", "capacitor-list-plugins": "capacitor", "change-detection": "components", + "chunk-sizes": "build", "clear-recordings": "http", "component-tree": "components", "defer-blocks": "components", @@ -46,6 +47,7 @@ "list-server-errors": "errors", "list-ssr-overrides": "http", "list-ssr-requests": "http", + "module-graph": "build", "navigate": "router", "ngrx-dispatch": "ngrx", "ngrx-export-session": "ngrx", @@ -88,7 +90,8 @@ "tanstack-run-query": "tanstackQuery", "track-subscriptions": "components", "wait-for": "components", - "watch-signal": "signals" + "watch-signal": "signals", + "web-vitals": "performance" }, "ACTION_TOOLS": { "forms": ["fill-form", "run-form-action"], @@ -182,6 +185,7 @@ "list-server-errors", "list-ssr-overrides", "list-ssr-requests", + "module-graph", "navigate", "ngrx-dispatch", "ngrx-export-session", @@ -214,7 +218,8 @@ "tanstack-run-query", "track-subscriptions", "wait-for", - "watch-signal" + "watch-signal", + "web-vitals" ], "agentTags": { "analog-explain-render-modes": ["analog"], @@ -232,6 +237,7 @@ "capacitor-list-calls": ["capacitor"], "capacitor-list-plugins": ["capacitor"], "change-detection": ["components"], + "chunk-sizes": ["build"], "clear-recordings": ["http", "router", "analog"], "component-tree": ["components"], "defer-blocks": ["components"], @@ -276,6 +282,7 @@ "list-server-errors": ["errors"], "list-ssr-overrides": ["http"], "list-ssr-requests": ["http"], + "module-graph": ["build"], "navigate": ["router"], "ngrx-dispatch": ["ngrx"], "ngrx-export-session": ["ngrx"], @@ -322,6 +329,7 @@ "tanstack-run-query": ["tanstackQuery"], "track-subscriptions": ["components"], "wait-for": ["components", "router", "http", "errors", "forms"], - "watch-signal": ["signals"] + "watch-signal": ["signals"], + "web-vitals": ["performance"] } } diff --git a/packages/devtools/src/__tests__/perf-collector.test.ts b/packages/devtools/src/__tests__/perf-collector.test.ts new file mode 100644 index 00000000..8f63753a --- /dev/null +++ b/packages/devtools/src/__tests__/perf-collector.test.ts @@ -0,0 +1,233 @@ +// @vitest-environment jsdom +import { afterEach, describe, expect, it } from 'vitest'; +import { elementById } from '../element-id.ts'; +import { + attachPerformance, + describeElement, + perfRating, + type PerfDebugNg, + type PerfEntry, + type PerfEnv, +} from '../perf-collector.ts'; +import { KEEPALIVE_MS } from '../change-detection.ts'; + +/** A browser stand-in: entries are delivered with `emit`, as a PerformanceObserver would. */ +function fakeEnv(supported: string[], navigation: PerfEntry[] = []) { + const listeners = new Map void>(); + const stopped: string[] = []; + const env: PerfEnv = { + observe(type, onEntries) { + if (!supported.includes(type)) return null; + listeners.set(type, onEntries); + return () => stopped.push(type); + }, + entries: (type) => (type === 'navigation' ? navigation : []), + }; + const emit = (type: string, entries: Partial[]) => + listeners.get(type)?.( + entries.map((e) => ({ entryType: type, startTime: 0, duration: 0, ...e }) as PerfEntry), + ); + return { env, emit, stopped, listeners }; +} + +class TripCard {} + +/** A fake `ng` whose only component owns everything under `host`. */ +function fakeNg(host: Element): PerfDebugNg { + const card = new TripCard(); + return { + getOwningComponent: (el) => (host.contains(el) ? card : null), + getHostElement: (component) => (component === card ? host : null), + }; +} + +function rpc() { + const calls: { name: string; args: unknown[] }[] = []; + return { + calls, + my: { + rpc: { + call: async (name: string, ...args: unknown[]) => { + calls.push({ name, args }); + return undefined; + }, + }, + }, + }; +} + +const ALL = ['largest-contentful-paint', 'paint', 'layout-shift', 'event', 'longtask']; + +afterEach(() => { + document.body.innerHTML = ''; +}); + +describe('attachPerformance', () => { + it('reads LCP with its element and owning component, by stable ids', () => { + document.body.innerHTML = ''; + const host = document.querySelector('app-trip-card')!; + const img = document.querySelector('img')!; + const { env, emit } = fakeEnv(ALL); + const perf = attachPerformance(rpc().my, 'tab1', () => fakeNg(host), { env }); + emit('largest-contentful-paint', [ + { startTime: 900, renderTime: 1200, element: img }, + { startTime: 2600, renderTime: 2700, element: img }, + ]); + const lcp = perf.report().metrics.LCP!; + expect(lcp).toMatchObject({ value: 2700, rating: 'needs-improvement', target: 'img#hero' }); + expect(lcp.owner!.name).toBe('TripCard'); + expect(elementById(lcp.owner!.id)).toBe(host); + // The same element gets the same id on the next report. + emit('largest-contentful-paint', [{ startTime: 2800, element: img }]); + expect(perf.report().metrics.LCP!.owner!.id).toBe(lcp.owner!.id); + }); + + it('sums layout shifts in session windows and skips shifts after input', () => { + const { env, emit } = fakeEnv(ALL); + const perf = attachPerformance(rpc().my, 'tab1', () => undefined, { env }); + expect(perf.report().metrics.CLS).toEqual({ value: 0, rating: 'good' }); + emit('layout-shift', [ + { startTime: 100, value: 0.05 }, + { startTime: 600, value: 0.04 }, + // Typing moved this one, so it doesn't count. + { startTime: 900, value: 0.5, hadRecentInput: true }, + // More than a second later: a new window, smaller than the first. + { startTime: 3000, value: 0.02 }, + ]); + expect(perf.report().metrics.CLS).toEqual({ value: 0.09, rating: 'good' }); + emit('layout-shift', [ + { startTime: 3500, value: 0.1 }, + { startTime: 4000, value: 0.1 }, + ]); + expect(perf.report().metrics.CLS).toEqual({ value: 0.22, rating: 'needs-improvement' }); + }); + + it('takes the slowest event of each interaction for INP and lists the slowest', () => { + document.body.innerHTML = ''; + const host = document.querySelector('app-trip-card')!; + const button = document.querySelector('button')!; + const { env, emit } = fakeEnv(ALL); + const perf = attachPerformance(rpc().my, 'tab1', () => fakeNg(host), { env }); + emit('event', [ + { name: 'pointerdown', interactionId: 1, duration: 80, startTime: 10, target: button }, + { name: 'click', interactionId: 1, duration: 320, startTime: 12, target: button }, + { name: 'keydown', interactionId: 2, duration: 120, startTime: 50 }, + // No interaction id: a hover, not an interaction. + { name: 'pointermove', duration: 900, startTime: 60 }, + ]); + const report = perf.report(); + expect(report.metrics.INP).toMatchObject({ + value: 320, + rating: 'needs-improvement', + event: 'click', + target: 'button#book', + }); + expect(report.metrics.INP!.owner!.name).toBe('TripCard'); + expect(report.interactions.map((i) => [i.event, i.durationMs])).toEqual([ + ['click', 320], + ['keydown', 120], + ]); + }); + + it('skips the slowest interaction per 50 for the 98th percentile', () => { + const { env, emit } = fakeEnv(ALL); + const perf = attachPerformance(rpc().my, 'tab1', () => undefined, { env }); + emit( + 'event', + Array.from({ length: 60 }, (_, i) => ({ + name: 'click', + interactionId: i + 1, + duration: i === 0 ? 2000 : 100 + i, + })), + ); + expect(perf.report().metrics.INP!.value).toBe(159); + }); + + it('counts long tasks and their blocking time, and prefers long animation frames', () => { + const tasks = fakeEnv(ALL); + const perf = attachPerformance(rpc().my, 'tab1', () => undefined, { env: tasks.env }); + tasks.emit('longtask', [ + { startTime: 100, duration: 120 }, + { startTime: 500, duration: 60 }, + ]); + expect(perf.report()).toMatchObject({ longTaskCount: 2, totalBlockingMs: 80 }); + const frames = fakeEnv([...ALL, 'long-animation-frame']); + const withFrames = attachPerformance(rpc().my, 'tab1', () => undefined, { env: frames.env }); + expect(frames.listeners.has('longtask')).toBe(false); + frames.emit('long-animation-frame', [ + { + startTime: 10, + duration: 90, + scripts: [ + { sourceURL: 'http://localhost/main.js?token=abc', duration: 70 }, + { invoker: 'Window.setTimeout', duration: 10 }, + ], + }, + ]); + const task = withFrames.report().longTasks[0]!; + expect(task).toMatchObject({ kind: 'long-animation-frame', durationMs: 90 }); + expect(task.source).toContain('main.js'); + }); + + it('reads FCP and TTFB, and says which entry types the browser observes', () => { + const { env, emit } = fakeEnv( + ['paint'], + [{ entryType: 'navigation', startTime: 0, duration: 0, responseStart: 950 }], + ); + const perf = attachPerformance(rpc().my, 'tab1', () => undefined, { env }); + emit('paint', [ + { name: 'first-paint', startTime: 500 }, + { name: 'first-contentful-paint', startTime: 700 }, + ]); + const report = perf.report(); + expect(report.metrics.FCP).toMatchObject({ value: 700, rating: 'good' }); + expect(report.metrics.TTFB).toMatchObject({ value: 950, rating: 'needs-improvement' }); + expect(report.metrics.CLS).toBeUndefined(); + expect(report.supported).toEqual(['paint']); + }); + + it('pushes on change only, again when the keepalive is due, and forgets the page on leave', async () => { + const { env, emit, stopped } = fakeEnv(ALL); + const { my, calls } = rpc(); + let now = 1_000; + const perf = attachPerformance(my, 'tab1', () => undefined, { env, now: () => now }); + await perf.push(); + await perf.push(); + expect(calls.filter((c) => c.name === 'push-performance')).toHaveLength(1); + emit('paint', [{ name: 'first-contentful-paint', startTime: 400 }]); + await perf.push(); + expect(calls.filter((c) => c.name === 'push-performance')).toHaveLength(2); + expect((calls.at(-1)!.args[0] as { pageId: string }).pageId).toBe('tab1'); + now += KEEPALIVE_MS + 1; + await perf.push(); + expect(calls.filter((c) => c.name === 'push-performance')).toHaveLength(3); + perf.leave(); + await perf.push(); + expect(calls.at(-1)).toEqual({ name: 'forget-performance-page', args: ['tab1'] }); + perf.stop(); + expect(stopped.sort()).toEqual([...ALL].sort()); + }); + + it('ignores a malformed entry', () => { + const { env, listeners } = fakeEnv(ALL); + const perf = attachPerformance(rpc().my, 'tab1', () => undefined, { env }); + expect(() => listeners.get('event')!(null as never)).not.toThrow(); + expect(perf.report().interactions).toEqual([]); + }); +}); + +describe('perf helpers', () => { + it('rates by the web.dev thresholds', () => { + expect(perfRating('LCP', 2500)).toBe('good'); + expect(perfRating('LCP', 2501)).toBe('needs-improvement'); + expect(perfRating('INP', 501)).toBe('poor'); + expect(perfRating('CLS', 0.1)).toBe('good'); + }); + + it('describes an element by tag, id or first class', () => { + document.body.innerHTML = '

'; + expect(describeElement(document.querySelector('p'))).toBe('p.lead'); + expect(describeElement(document.querySelector('div'))).toBe('div#x'); + expect(describeElement(null)).toBeUndefined(); + }); +}); diff --git a/packages/devtools/src/__tests__/perf-server.test.ts b/packages/devtools/src/__tests__/perf-server.test.ts new file mode 100644 index 00000000..667d3b97 --- /dev/null +++ b/packages/devtools/src/__tests__/perf-server.test.ts @@ -0,0 +1,128 @@ +import { createHostContext } from 'devframe/node'; +import { describe, expect, it } from 'vitest'; +import pangular, { createPangular } from '../devframe.ts'; +import { stalePerfPages, toPerfPage, webVitalsText, type PerfState } from '../rpc/perf-register.ts'; +import { answerText } from './tool-answer.ts'; + +const JWT = ['eyJhbGciOiJIUzI1NiJ9', 'eyJzdWIiOiIxMjM0NSJ9', 'c2lnbmF0dXJlc2ln'].join('.'); + +const report = (extra: Record = {}) => ({ + pageId: 'tab1', + url: '/trips', + title: 'Trips', + metrics: { + LCP: { + value: 3100, + rating: 'good', + target: 'img#hero', + owner: { id: 'c1-2', name: 'TripCard' }, + at: 3100, + }, + CLS: { value: 0.02, rating: 'good' }, + INP: { value: 240, rating: 'needs-improvement', event: 'click', target: 'button#book' }, + }, + interactions: [{ event: 'click', durationMs: 240, at: 5000, target: 'button#book' }], + longTasks: [{ at: 100, durationMs: 120, kind: 'longtask' }], + longTaskCount: 1, + totalBlockingMs: 70, + supported: ['largest-contentful-paint', 'layout-shift', 'event', 'longtask', 'bogus'], + ...extra, +}); + +describe('toPerfPage', () => { + it('keeps a valid report, rates it on the server and masks text', () => { + const page = toPerfPage(report({ url: `/trips?token=${JWT}` }), 42)!; + // The page said good; 3.1 s is not. + expect(page.metrics.LCP!.rating).toBe('needs-improvement'); + expect(page.url).not.toContain(JWT); + expect(page.supported).toEqual([ + 'largest-contentful-paint', + 'layout-shift', + 'event', + 'longtask', + ]); + expect(page.reportedAt).toBe(42); + }); + + it('drops bad values and refuses a report without a page id', () => { + expect(toPerfPage({})).toBeNull(); + expect(toPerfPage(report({ pageId: 'x'.repeat(60) }))).toBeNull(); + const page = toPerfPage( + report({ + metrics: { LCP: { value: -5 }, CLS: { value: 'big' }, FCP: { value: 900 } }, + interactions: [null, { event: 'click' }], + longTasks: 'none', + longTaskCount: -1, + }), + )!; + expect(Object.keys(page.metrics)).toEqual(['FCP']); + expect(page.interactions).toEqual([]); + expect(page.longTasks).toEqual([]); + expect(page.longTaskCount).toBe(0); + }); + + it('expires pages past their TTL', () => { + const state: PerfState = { pages: { tab1: toPerfPage(report(), 1000)! } }; + expect(stalePerfPages(state, 20_000, () => 30_000)).toEqual([]); + expect(stalePerfPages(state, 40_000, () => 30_000)).toEqual(['tab1']); + }); +}); + +describe('web-vitals text', () => { + it('lists each metric with rating and where it happened', () => { + const state: PerfState = { pages: { tab1: toPerfPage(report())! } }; + const text = webVitalsText(state); + expect(text).toContain('| Largest Contentful Paint (LCP) | 3.10 s | needs improvement |'); + expect(text).toContain('`TripCard`'); + expect(text).toContain('| First Contentful Paint (FCP) | none yet |'); + expect(text).toContain('Long tasks: 1, total blocking time 70 ms.'); + expect(webVitalsText({ pages: {} })).toMatch(/^No page reports web vitals/); + expect(webVitalsText(state, { page: 'gone' })).toContain('gone'); + }); +}); + +async function boot(definition = pangular) { + const host = { + mountStatic: () => {}, + resolveOrigin: () => 'http://localhost', + getStorageDir: () => '', + }; + const ctx = await createHostContext({ cwd: process.cwd(), mode: 'dev', host: host as never }); + await definition.setup(ctx as never); + const call = (name: string, payload: unknown) => + (ctx.rpc.invokeLocal as (name: string, payload: unknown) => Promise)( + `pangular:${name}`, + payload, + ); + const invoke = async (id: string, args: Record = {}) => + await answerText(ctx.agent.invoke(`pangular:${id}`, args)); + const state = async () => + ( + await ( + ctx.rpc as unknown as { + sharedState: { get: (key: string) => Promise<{ value: () => PerfState }> }; + } + ).sharedState.get('pangular:performance') + ).value(); + return { ctx, call, invoke, state }; +} + +describe('performance on the devframe server', () => { + it('keeps one entry per page, answers web-vitals and forgets a page', async () => { + const { call, invoke, state } = await boot(); + await call('push-performance', report()); + await call('push-performance', report({ pageId: 'tab2', url: '/other' })); + expect(Object.keys((await state()).pages).sort()).toEqual(['tab1', 'tab2']); + expect(await invoke('web-vitals', { page: 'tab2' })).toContain('/other'); + await call('forget-performance-page', 'tab1'); + expect(Object.keys((await state()).pages)).toEqual(['tab2']); + }); + + it('is a read tool that disappears with the performance inspector', async () => { + const { ctx } = await boot(); + expect(ctx.agent.list().tools.find((t) => t.id === 'pangular:web-vitals')?.safety).toBe('read'); + const off = await boot(createPangular({ inspectors: { performance: false } })); + expect(off.ctx.agent.list().tools.map((t) => t.id)).not.toContain('pangular:web-vitals'); + await expect(off.call('push-performance', report())).rejects.toThrow(); + }); +}); diff --git a/packages/devtools/src/__tests__/vite-errors.test.ts b/packages/devtools/src/__tests__/vite-errors.test.ts index 87e51df9..5f61ee88 100644 --- a/packages/devtools/src/__tests__/vite-errors.test.ts +++ b/packages/devtools/src/__tests__/vite-errors.test.ts @@ -1,5 +1,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { serverErrorsRegistry } from '../server-errors.ts'; +import { buildRegistry } from '../build-graph.ts'; vi.mock('../hub.ts', () => ({ PANGULAR_HUB_BASE: '/__devframes/', @@ -18,12 +19,14 @@ function serve(options: Parameters[0]) { middlewares: { use: vi.fn() }, httpServer: null, ws, + environments: { client: { moduleGraph: { idToModuleMap: new Map() } } }, }); return { ws, sent, original }; } afterEach(() => { delete (globalThis as { __PANGULAR_SERVER_ERRORS__?: unknown }).__PANGULAR_SERVER_ERRORS__; + delete (globalThis as { __PANGULAR_BUILD__?: unknown }).__PANGULAR_BUILD__; }); describe('Vite plugin error capture', () => { @@ -40,4 +43,12 @@ describe('Vite plugin error capture', () => { const { ws, original } = serve({ inspectors: { errors: false } }); expect(ws.send).toBe(original); }); + + it("hands Vite's client module graph to the Build inspector, unless it is off", () => { + serve({}); + expect(buildRegistry().vite).toBeDefined(); + delete buildRegistry().vite; + serve({ inspectors: { build: false } }); + expect(buildRegistry().vite).toBeUndefined(); + }); }); diff --git a/packages/devtools/src/build-graph.ts b/packages/devtools/src/build-graph.ts new file mode 100644 index 00000000..469a2841 --- /dev/null +++ b/packages/devtools/src/build-graph.ts @@ -0,0 +1,343 @@ +// The module graph of the Vite dev server and the chunk sizes of the last +// production build, for the Build tab and the `module-graph` and +// `chunk-sizes` agent tools. Both are read on request: the dev graph from +// Vite's own module graph (the Vite plugin hands it over through a registry +// on `globalThis`), the chunks from the browser build output on disk. + +import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'; +import { basename, dirname, join, relative, resolve, sep } from 'node:path'; +import { gzipSync } from 'node:zlib'; + +/** One module as the Vite dev server knows it. */ +export interface GraphModule { + /** The served URL, such as `/src/app/app.ts`. */ + url: string; + /** The file, relative to the project root when it is inside it. */ + file?: string; + type: 'js' | 'css' | 'asset'; + /** Bytes of the transformed code Vite serves; 0 until the module was requested. */ + size: number; + importers: number; + imports: number; + /** URLs this module imports with `import()`. */ + dynamicImports: string[]; +} + +/** A module loaded with `import()`, and what it pulls in that the entry doesn't. */ +export interface LazyBoundary { + url: string; + file?: string; + /** Who imports it lazily, such as `app.routes.ts`. */ + importers: string[]; + /** Modules reached only through this boundary. */ + modules: number; + /** Their transformed size, unminified. */ + size: number; +} + +export interface ViteGraph { + modules: GraphModule[]; + /** Total transformed size of every module Vite served. */ + totalSize: number; + /** Modules no other module imports, such as `main.ts`. */ + entries: string[]; + /** Transformed size reachable from the entries without `import()`. */ + initialSize: number; + lazy: LazyBoundary[]; +} + +export interface BuildChunk { + file: string; + bytes: number; + gzip: number; + /** `index.html` loads it before the app starts (a script, a module preload or a stylesheet). */ + initial: boolean; + type: 'js' | 'css'; + /** The source the chunk starts from, from `stats.json` (`ng build --stats-json`). */ + entryPoint?: string; + /** Its biggest inputs, from `stats.json`. */ + inputs?: { file: string; bytes: number }[]; +} + +export interface BuildOutput { + /** The browser output folder, relative to the project root. */ + dir: string; + builtAt: number; + chunks: BuildChunk[]; + initialBytes: number; + initialGzip: number; + lazyBytes: number; + /** Whether a `stats.json` metafile was found next to the output. */ + stats: boolean; +} + +export interface BuildGraph { + vite?: ViteGraph; + /** Why there is no dev graph. */ + viteNote?: string; + output?: BuildOutput; + outputNote?: string; +} + +/** The part of a Vite module node the graph reads; `staticImportedUrls` is internal and optional. */ +export interface ViteModuleLike { + url: string; + file: string | null; + type: string; + importers: Set; + importedModules: Set; + transformResult: { code?: string } | null; + staticImportedUrls?: Set; +} + +export interface ViteGraphSource { + modules(): Iterable; + root: string; +} + +export interface BuildRegistry { + /** Set by the Vite plugin while its dev server runs. */ + vite?: ViteGraphSource; +} + +export const MAX_GRAPH_MODULES = 5000; +const MAX_CHUNKS = 500; + +export function buildRegistry(): BuildRegistry { + const g = globalThis as { __PANGULAR_BUILD__?: BuildRegistry }; + return (g.__PANGULAR_BUILD__ ??= {}); +} + +/** The client module graph of a Vite dev server (`environments.client`, or `moduleGraph` before Vite 6). */ +export function viteGraphSource(server: { + config?: { root?: string }; + environments?: Record } }>; + moduleGraph?: { idToModuleMap?: Map }; +}): ViteGraphSource | undefined { + const graph = server.environments?.['client']?.moduleGraph ?? server.moduleGraph; + const map = graph?.idToModuleMap; + if (!map) return undefined; + return { + root: server.config?.root ?? process.cwd(), + modules: () => map.values() as Iterable, + }; +} + +const rel = (root: string, file: string | null): string | undefined => { + if (!file) return undefined; + const path = relative(root, file); + return path && !path.startsWith('..') ? path.split(sep).join('/') : file; +}; + +const sizeOf = (mod: ViteModuleLike) => mod.transformResult?.code?.length ?? 0; + +function isStatic(from: ViteModuleLike, to: ViteModuleLike): boolean { + // Without Vite's internal list, every import counts as static. + return from.staticImportedUrls ? from.staticImportedUrls.has(to.url) : true; +} + +/** Everything `start` reaches through static imports, `start` included. */ +function staticClosure(start: Iterable): Set { + const seen = new Set(); + const queue = [...start]; + while (queue.length) { + const mod = queue.pop()!; + if (seen.has(mod)) continue; + seen.add(mod); + for (const next of mod.importedModules) if (isStatic(mod, next)) queue.push(next); + } + return seen; +} + +/** Reads the dev server's module graph into the shape the panel and tools show. */ +export function readViteGraph(source: ViteGraphSource): ViteGraph { + const all = [...source.modules()].slice(0, MAX_GRAPH_MODULES); + const entries = all.filter( + (mod) => mod.type === 'js' && mod.importers.size === 0 && !mod.url.startsWith('/@'), + ); + const initial = staticClosure(entries); + const lazyTargets = new Map>(); + for (const mod of all) { + for (const next of mod.importedModules) { + if (isStatic(mod, next)) continue; + if (!lazyTargets.has(next)) lazyTargets.set(next, new Set()); + lazyTargets.get(next)!.add(mod); + } + } + const lazy: LazyBoundary[] = [...lazyTargets].map(([target, importers]) => { + const only = [...staticClosure([target])].filter((mod) => !initial.has(mod)); + return { + url: target.url, + ...(rel(source.root, target.file) ? { file: rel(source.root, target.file) } : {}), + importers: [...importers].map((mod) => rel(source.root, mod.file) ?? mod.url), + modules: only.length, + size: only.reduce((sum, mod) => sum + sizeOf(mod), 0), + }; + }); + lazy.sort((a, b) => b.size - a.size); + const modules: GraphModule[] = all.map((mod) => { + const file = rel(source.root, mod.file); + return { + url: mod.url, + ...(file ? { file } : {}), + type: mod.type === 'css' ? 'css' : mod.type === 'asset' ? 'asset' : 'js', + size: sizeOf(mod), + importers: mod.importers.size, + imports: mod.importedModules.size, + dynamicImports: [...mod.importedModules].filter((n) => !isStatic(mod, n)).map((n) => n.url), + }; + }); + modules.sort((a, b) => b.size - a.size); + return { + modules, + totalSize: modules.reduce((sum, mod) => sum + mod.size, 0), + entries: entries.map((mod) => mod.url), + initialSize: [...initial].reduce((sum, mod) => sum + sizeOf(mod), 0), + lazy, + }; +} + +function readJson(file: string): unknown { + try { + return JSON.parse(readFileSync(file, 'utf8')); + } catch { + return undefined; + } +} + +/** + * The browser output folder of the app's production build: `outputPath` of + * the first application in `angular.json` (its `browser` folder), else + * `dist/browser` or `dist`. `hint` wins when it exists. + */ +export function findBuildOutput(root: string, hint?: string): string | undefined { + const candidates: string[] = []; + if (hint) candidates.push(resolve(root, hint)); + const workspace = readJson(join(root, 'angular.json')) as + | { projects?: Record }> } + | undefined; + for (const project of Object.values(workspace?.projects ?? {})) { + if (project.projectType && project.projectType !== 'application') continue; + const build = project.architect?.['build'] as + { options?: { outputPath?: string | { base?: string; browser?: string } } } | undefined; + const output = build?.options?.outputPath; + if (typeof output === 'string') { + candidates.push(join(root, output, 'browser'), join(root, output)); + } else if (output?.base) { + candidates.push(join(root, output.base, output.browser ?? 'browser')); + } + } + candidates.push(join(root, 'dist', 'browser'), join(root, 'dist')); + return candidates.find((dir) => existsSync(join(dir, 'index.html')) || hasScripts(dir)); +} + +function hasScripts(dir: string): boolean { + try { + return readdirSync(dir).some((name) => /\.m?js$/.test(name)); + } catch { + return false; + } +} + +/** Files `index.html` loads up front: scripts, module preloads and stylesheets. */ +export function initialFiles(html: string): Set { + const files = new Set(); + const pattern = /<(script|link)\b[^>]*?(?:src|href)=["']([^"']+)["'][^>]*>/gi; + for (const match of html.matchAll(pattern)) { + const tag = match[0]; + if (match[1]!.toLowerCase() === 'link' && !/rel=["']?(modulepreload|stylesheet)/i.test(tag)) { + continue; + } + const url = match[2]!; + if (/^[a-z]+:\/\//i.test(url) || url.startsWith('//')) continue; + files.add(basename(url.split(/[?#]/)[0]!)); + } + return files; +} + +interface Metafile { + outputs?: Record< + string, + { bytes?: number; entryPoint?: string; inputs?: Record } + >; +} + +/** Lists the JS and CSS chunks of a browser build with their sizes. */ +export function readBuildOutput(root: string, dir: string): BuildOutput { + const html = existsSync(join(dir, 'index.html')) + ? readFileSync(join(dir, 'index.html'), 'utf8') + : ''; + const initial = initialFiles(html); + const metafile = (readJson(join(dir, 'stats.json')) ?? + readJson(join(dirname(dir), 'stats.json'))) as Metafile | undefined; + const outputs = metafile?.outputs ?? {}; + const byName = new Map(Object.entries(outputs).map(([path, out]) => [basename(path), out])); + const files = readdirSync(dir) + .filter((name) => /\.(m?js|css)$/.test(name)) + .slice(0, MAX_CHUNKS); + let builtAt = 0; + const chunks: BuildChunk[] = files.map((name) => { + const path = join(dir, name); + const stat = statSync(path); + builtAt = Math.max(builtAt, stat.mtimeMs); + const content = readFileSync(path); + const meta = byName.get(name); + const inputs = Object.entries(meta?.inputs ?? {}) + .map(([file, input]) => ({ file, bytes: input.bytesInOutput ?? 0 })) + .sort((a, b) => b.bytes - a.bytes) + .slice(0, 5); + return { + file: name, + bytes: stat.size, + gzip: gzipSync(content).length, + initial: initial.has(name), + type: name.endsWith('.css') ? 'css' : 'js', + ...(meta?.entryPoint ? { entryPoint: meta.entryPoint } : {}), + ...(inputs.length ? { inputs } : {}), + }; + }); + chunks.sort((a, b) => Number(b.initial) - Number(a.initial) || b.bytes - a.bytes); + const sum = (list: BuildChunk[], key: 'bytes' | 'gzip') => + list.reduce((total, chunk) => total + chunk[key], 0); + const first = chunks.filter((chunk) => chunk.initial); + return { + dir: rel(root, dir) ?? dir, + builtAt: Math.round(builtAt), + chunks, + initialBytes: sum(first, 'bytes'), + initialGzip: sum(first, 'gzip'), + lazyBytes: sum( + chunks.filter((chunk) => !chunk.initial), + 'bytes', + ), + stats: !!metafile?.outputs, + }; +} + +/** The dev graph and the build output, each with a note when it is missing. */ +export function readBuildGraph(root: string, options: { outputDir?: string } = {}): BuildGraph { + const graph: BuildGraph = {}; + const source = buildRegistry().vite; + if (source) { + try { + graph.vite = readViteGraph(source); + } catch { + graph.viteNote = "Vite's module graph could not be read."; + } + } else { + graph.viteNote = + 'No Vite dev server in this process. The module graph needs the Vite plugin (@pangular-inspector/devtools/vite).'; + } + const dir = findBuildOutput(root, options.outputDir); + if (dir) { + try { + graph.output = readBuildOutput(root, dir); + } catch { + graph.outputNote = `The build output in ${rel(root, dir) ?? dir} could not be read.`; + } + } else { + graph.outputNote = + 'No production build found (the outputPath in angular.json, dist/browser or dist). Run the production build, with --stats-json for the source of each chunk.'; + } + return graph; +} diff --git a/packages/devtools/src/config.ts b/packages/devtools/src/config.ts index a29b1660..36eb7e1e 100644 --- a/packages/devtools/src/config.ts +++ b/packages/devtools/src/config.ts @@ -12,6 +12,8 @@ export const PANGULAR_INSPECTORS = [ 'capacitor', 'errors', 'plugins', + 'performance', + 'build', ] as const; export type PangularInspector = (typeof PANGULAR_INSPECTORS)[number]; @@ -397,6 +399,8 @@ export const PANEL_TABS = [ { id: 'pipes', label: 'Pipes', inspector: 'pipes' }, { id: 'network', label: 'SSR & HTTP', inspector: 'http' }, { id: 'errors', label: 'Errors', inspector: 'errors' }, + { id: 'performance', label: 'Performance', inspector: 'performance' }, + { id: 'build', label: 'Build', inspector: 'build' }, { id: 'capacitor', label: 'Capacitor', inspector: 'capacitor' }, { id: 'plugins', label: 'Plugins', inspector: 'plugins' }, ] as const satisfies readonly { id: string; label: string; inspector?: PangularInspector }[]; @@ -1241,6 +1245,11 @@ export const AGENT_ENTRIES: Readonly> = { 'list-console': { kind: 'tool', inspectors: ['errors'], page: true }, // SSR and Vite errors the server captured in its own process; the stdio MCP server has none. 'list-server-errors': { kind: 'tool', inspectors: ['errors'], page: true }, + 'web-vitals': { kind: 'tool', inspectors: ['performance'], page: true }, + // Vite's module graph lives in the dev server's process; the stdio MCP server has none. + 'module-graph': { kind: 'tool', inspectors: ['build'], page: true }, + // Reads the build output on disk, so it works without a page. + 'chunk-sizes': { kind: 'tool', inspectors: ['build'] }, // plugins 'list-plugins': { kind: 'tool', inspectors: ['plugins'], page: true }, @@ -1470,6 +1479,9 @@ export const RPC_INSPECTOR: Record = { 'push-errors': 'errors', 'forget-errors-page': 'errors', 'clear-server-errors': 'errors', + 'push-performance': 'performance', + 'forget-performance-page': 'performance', + 'get-build-graph': 'build', 'push-analog': 'analog', 'forget-analog-page': 'analog', 'analog-clear-calls': 'analog', @@ -1521,6 +1533,7 @@ export const SHARED_STATE_INSPECTOR: Record = { analog: 'analog', errors: 'errors', 'server-errors': 'errors', + performance: 'performance', startup: 'injectors', plugins: 'plugins', }; @@ -1762,6 +1775,8 @@ const INSPECTOR_LABEL: Record = { capacitor: 'Capacitor', errors: 'Errors', plugins: 'Plugins', + performance: 'Performance', + build: 'Build', }; const ACTION_LABEL: Record = { diff --git a/packages/devtools/src/devframe.ts b/packages/devtools/src/devframe.ts index 2a55611c..67f5cbef 100644 --- a/packages/devtools/src/devframe.ts +++ b/packages/devtools/src/devframe.ts @@ -24,6 +24,8 @@ import { import { registerConsoleGlobals } from './rpc/console-globals.ts'; import { registerErrors } from './rpc/errors-register.ts'; import { registerServerErrors } from './rpc/server-errors-register.ts'; +import { registerPerformance } from './rpc/perf-register.ts'; +import { registerBuild } from './rpc/build-register.ts'; import { registerSnapshot } from './rpc/snapshot-tools.ts'; import { registerLeaks } from './rpc/leaks-register.ts'; import { registerReduxStores } from './rpc/redux-stores-register.ts'; @@ -949,6 +951,18 @@ const pangular = defineDevframe({ const serverErrors = on.errors ? await registerServerErrors({ my, register, agent, capture: pageTools }) : null; + const perfInspector = on.performance + ? await registerPerformance({ + my, + rpcHost: ctx.rpc, + register, + agent, + owns, + mayForget, + ttl: liveTtl, + }) + : null; + if (on.build) registerBuild({ register, agent, root: ctx.cwd }); const leaksInspector = on.components ? await registerLeaks({ my, @@ -1481,6 +1495,7 @@ const pangular = defineDevframe({ if (expireCapacitorPages(capacitorPages, now, liveTtl)) applyCapacitor(); errorsInspector?.expire(now); serverErrors?.expire(now); + perfInspector?.expire(now); leaksInspector?.expire(now); reduxStores?.expire(now); tanstackQuery?.expire(now); diff --git a/packages/devtools/src/overlay.ts b/packages/devtools/src/overlay.ts index a7f896cf..97a36759 100644 --- a/packages/devtools/src/overlay.ts +++ b/packages/devtools/src/overlay.ts @@ -19,6 +19,7 @@ import { attachErrors } from './errors-collector.ts'; import { attachLeaks } from './leaks-collector.ts'; import { attachPlugins } from './plugins-collector.ts'; import { attachStartup } from './startup-timing.ts'; +import { attachPerformance } from './perf-collector.ts'; import { attachBrowserTools } from './browser-tools.ts'; import { attachAxe, axeOwnerNg } from './axe-runner.ts'; import { createRecentWrites } from './error-explain.ts'; @@ -597,6 +598,8 @@ async function startOverlay( }) : null; const startup = on.injectors ? attachStartup(my, pageId, getNg, { tickMs }) : null; + const perf = on.performance ? attachPerformance(my, pageId, getNg, { tickMs }) : null; + if (perf) own(perf.stop); // Runs axe-core only when the panel or an agent asks; it loads axe-core then. // The component owning each element is component data: only with that inspector on. const axe = attachAxe(my, pageId, axeOwnerNg(on.components, getNg)); @@ -822,6 +825,7 @@ async function startOverlay( }), plugins && paced(() => void plugins.push().catch(() => {})), startup && paced(() => void startup.push().catch(() => {})), + perf && paced(() => void perf.push().catch(() => {})), ].filter((collect) => typeof collect === 'function'); const pushAll = () => collectors.forEach((run) => run()); pushAll(); @@ -1078,6 +1082,7 @@ async function startOverlay( leaks?.leave(); plugins?.leave(); startup?.leave(); + perf?.leave(); angularInfo.leave(); browserTools.leave(); }; @@ -1092,6 +1097,7 @@ async function startOverlay( leaks?.resume(); plugins?.resume(); startup?.resume(); + perf?.resume(); browserTools.resume(); void lease.refresh(); left = false; diff --git a/packages/devtools/src/perf-collector.ts b/packages/devtools/src/perf-collector.ts new file mode 100644 index 00000000..452fc108 --- /dev/null +++ b/packages/devtools/src/perf-collector.ts @@ -0,0 +1,395 @@ +// Web vitals and long tasks of the page, read from the browser's own +// PerformanceObserver entries: Largest Contentful Paint, Cumulative Layout +// Shift, Interaction to Next Paint, First Contentful Paint, Time to First +// Byte, and long tasks (or long animation frames where the browser has them). +// It only observes: nothing runs on a timer, and the report goes out with the +// overlay's regular pushes, skipped while it is unchanged. + +import { keepaliveDue } from './change-detection.ts'; +import { elementId } from './element-id.ts'; +import { className } from './injector-tree.ts'; +import { outsideAngular } from './outside-angular.ts'; +import { redactMessage, redactUrl } from './router.ts'; + +export type PerfRating = 'good' | 'needs-improvement' | 'poor'; +export type PerfMetricName = 'LCP' | 'CLS' | 'INP' | 'FCP' | 'TTFB'; + +/** Thresholds from web.dev: at or under the first is good, over the second is poor. */ +export const PERF_THRESHOLDS: Record = { + LCP: [2500, 4000], + CLS: [0.1, 0.25], + INP: [200, 500], + FCP: [1800, 3000], + TTFB: [800, 1800], +}; + +export function perfRating(name: PerfMetricName, value: number): PerfRating { + const [good, poor] = PERF_THRESHOLDS[name]; + return value <= good ? 'good' : value <= poor ? 'needs-improvement' : 'poor'; +} + +/** The component an element belongs to, by the ids the Components tab uses. */ +export interface PerfOwner { + id: string; + name: string; +} + +export interface PerfMetric { + /** Milliseconds, or a unitless score for CLS. */ + value: number; + rating: PerfRating; + /** LCP: the element; INP: the event target. A tag with its id or first class. */ + target?: string; + owner?: PerfOwner; + /** INP: the event type, such as `click`. */ + event?: string; + /** When it happened, ms from navigation start. */ + at?: number; +} + +export interface PerfInteraction { + event: string; + durationMs: number; + at: number; + target?: string; + owner?: PerfOwner; +} + +export interface PerfLongTask { + at: number; + durationMs: number; + /** `longtask` or `long-animation-frame`. */ + kind: string; + /** The script that ran longest in a long animation frame, when the browser names it. */ + source?: string; +} + +export interface PerfReport { + pageId: string; + url?: string; + title?: string; + metrics: Partial>; + /** The slowest interactions, slowest first. */ + interactions: PerfInteraction[]; + /** The latest long tasks, newest last. */ + longTasks: PerfLongTask[]; + longTaskCount: number; + /** Sum of each long task's time over 50 ms. */ + totalBlockingMs: number; + /** Entry types this browser observes; a missing one explains a missing metric. */ + supported: string[]; +} + +export const MAX_INTERACTIONS = 10; +export const MAX_LONG_TASKS = 20; + +/** The part of `ng` the owner lookup reads. */ +export interface PerfDebugNg { + getOwningComponent?(el: Element): unknown; + getHostElement?(component: object): Element | null; +} + +interface Rpc { + rpc: { call(name: string, ...args: unknown[]): Promise }; +} + +/** What a PerformanceObserver entry has; jsdom and older browsers lack most types. */ +export interface PerfEntry { + entryType: string; + name?: string; + startTime: number; + duration: number; + value?: number; + hadRecentInput?: boolean; + interactionId?: number; + element?: Element | null; + target?: Node | null; + renderTime?: number; + loadTime?: number; + responseStart?: number; + scripts?: { sourceURL?: string; invoker?: string; duration?: number }[]; +} + +export interface PerfEnv { + /** Subscribes to entries of one type; returns the stop function, or null when unsupported. */ + observe(type: string, onEntries: (entries: PerfEntry[]) => void): (() => void) | null; + /** Entries the browser already has, such as the navigation entry. */ + entries(type: string): PerfEntry[]; +} + +const round = (value: number, digits = 0) => { + const f = 10 ** digits; + return Math.round(value * f) / f; +}; + +function browserEnv(): PerfEnv { + return { + observe(type, onEntries) { + if (typeof PerformanceObserver === 'undefined') return null; + const types = (PerformanceObserver as { supportedEntryTypes?: readonly string[] }) + .supportedEntryTypes; + if (!types?.includes(type)) return null; + try { + // Created outside the Angular zone, so an entry never makes a zone.js app tick. + const observer = outsideAngular( + () => + new PerformanceObserver((list) => + onEntries(list.getEntries() as unknown as PerfEntry[]), + ), + ); + const options: PerformanceObserverInit & { durationThreshold?: number } = { + type, + buffered: true, + }; + // Interactions of 40 ms and more, the floor the spec allows; INP is about slow ones. + if (type === 'event') options.durationThreshold = 40; + observer.observe(options); + return () => observer.disconnect(); + } catch { + return null; + } + }, + entries(type) { + try { + return performance.getEntriesByType(type) as unknown as PerfEntry[]; + } catch { + return []; + } + }, + }; +} + +/** A short label for an element: its tag with the id, else the first class. */ +export function describeElement(el: unknown): string | undefined { + if (!el || typeof el !== 'object' || !('tagName' in el)) return undefined; + const element = el as Element; + const tag = String(element.tagName).toLowerCase(); + const id = element.id ? `#${element.id}` : ''; + const cls = !id && element.classList?.length ? `.${element.classList[0]}` : ''; + return redactMessage(`${tag}${id}${cls}`).slice(0, 120); +} + +/** + * Collects the page's web vitals. Each `push()` sends the report when it + * changed (or the page heartbeat says the server lost it). + */ +export function attachPerformance( + my: Rpc, + pageId: string, + getNg: () => PerfDebugNg | undefined, + options: { tickMs?: () => number; now?: () => number; env?: PerfEnv } = {}, +) { + const env = options.env ?? browserEnv(); + const tickMs = options.tickMs ?? (() => 0); + const now = options.now ?? Date.now; + let lcp: PerfMetric | undefined; + let fcp: number | undefined; + // CLS: the largest session window of shifts less than 1 s apart and at most 5 s long. + let cls = 0; + let session = 0; + let sessionStart = -Infinity; + let lastShift = -Infinity; + const interactions = new Map(); + const longTasks: PerfLongTask[] = []; + let longTaskCount = 0; + let totalBlockingMs = 0; + const supported: string[] = []; + let lastJson = ''; + let sentAt = 0; + let left = false; + + const ownerOf = (el: unknown): PerfOwner | undefined => { + const ng = getNg(); + if (!ng?.getOwningComponent || !ng.getHostElement || !el || typeof el !== 'object') { + return undefined; + } + try { + const component = ng.getOwningComponent(el as Element); + if (!component || typeof component !== 'object') return undefined; + const host = ng.getHostElement(component); + if (!host) return undefined; + return { id: elementId(host), name: className((component as object).constructor) }; + } catch { + return undefined; + } + }; + + const handlers: Record void> = { + 'largest-contentful-paint': (entries) => { + const entry = entries.at(-1); + if (!entry) return; + const value = round(entry.renderTime || entry.loadTime || entry.startTime); + const target = describeElement(entry.element); + const owner = ownerOf(entry.element); + lcp = { + value, + rating: perfRating('LCP', value), + at: value, + ...(target ? { target } : {}), + ...(owner ? { owner } : {}), + }; + }, + paint: (entries) => { + const entry = entries.find((e) => e.name === 'first-contentful-paint'); + if (entry) fcp = round(entry.startTime); + }, + 'layout-shift': (entries) => { + for (const entry of entries) { + if (entry.hadRecentInput || typeof entry.value !== 'number') continue; + const at = entry.startTime; + if (at - lastShift < 1000 && at - sessionStart < 5000) session += entry.value; + else { + session = entry.value; + sessionStart = at; + } + lastShift = at; + cls = Math.max(cls, session); + } + }, + event: (entries) => { + for (const entry of entries) { + if (!entry.interactionId) continue; + const known = interactions.get(entry.interactionId); + if (known && known.durationMs >= entry.duration) continue; + const target = describeElement(entry.target); + const owner = ownerOf(entry.target); + interactions.set(entry.interactionId, { + event: String(entry.name ?? 'event').slice(0, 40), + durationMs: round(entry.duration), + at: round(entry.startTime), + ...(target ? { target } : {}), + ...(owner ? { owner } : {}), + }); + } + // Keeps the map small: only the slowest interactions can be INP. + if (interactions.size > 200) { + const keep = [...interactions].sort((a, b) => b[1].durationMs - a[1].durationMs); + interactions.clear(); + for (const [id, value] of keep.slice(0, 100)) interactions.set(id, value); + } + }, + // Before `longtask`, so a browser with both reports frames only. + 'long-animation-frame': (entries) => addLongTasks(entries, 'long-animation-frame'), + longtask: (entries) => addLongTasks(entries, 'longtask'), + }; + + function addLongTasks(entries: PerfEntry[], kind: string) { + for (const entry of entries) { + longTaskCount++; + totalBlockingMs += Math.max(0, entry.duration - 50); + const script = entry.scripts + ?.slice() + .sort((a, b) => (b.duration ?? 0) - (a.duration ?? 0))[0]; + const source = script?.sourceURL || script?.invoker; + longTasks.push({ + at: round(entry.startTime), + durationMs: round(entry.duration), + kind, + ...(source ? { source: redactUrl(String(source)).slice(0, 300) } : {}), + }); + if (longTasks.length > MAX_LONG_TASKS) longTasks.shift(); + } + } + + const stops: (() => void)[] = []; + for (const [type, handler] of Object.entries(handlers)) { + // Long animation frames say more than long tasks; with both, only frames count. + if (type === 'longtask' && supported.includes('long-animation-frame')) continue; + const stop = env.observe(type, (entries) => { + try { + handler(entries); + } catch { + // A malformed entry never breaks the page. + } + }); + if (stop) { + stops.push(stop); + supported.push(type); + } + } + + const ttfb = (): number | undefined => { + const nav = env.entries('navigation')[0]; + const value = nav?.responseStart; + return typeof value === 'number' && value > 0 ? round(value) : undefined; + }; + + const inp = (): PerfMetric | undefined => { + const all = [...interactions.values()].sort((a, b) => b.durationMs - a.durationMs); + if (!all.length) return undefined; + // The 98th percentile: skip one of the slowest per 50 interactions. + const worst = all[Math.min(all.length - 1, Math.floor(interactions.size / 50))]!; + return { + value: worst.durationMs, + rating: perfRating('INP', worst.durationMs), + event: worst.event, + at: worst.at, + ...(worst.target ? { target: worst.target } : {}), + ...(worst.owner ? { owner: worst.owner } : {}), + }; + }; + + const report = (): PerfReport => { + const metrics: PerfReport['metrics'] = {}; + if (lcp) metrics.LCP = lcp; + if (supported.includes('layout-shift')) { + metrics.CLS = { value: round(cls, 4), rating: perfRating('CLS', cls) }; + } + const interaction = inp(); + if (interaction) metrics.INP = interaction; + if (fcp !== undefined) metrics.FCP = { value: fcp, rating: perfRating('FCP', fcp), at: fcp }; + const firstByte = ttfb(); + if (firstByte !== undefined) { + metrics.TTFB = { value: firstByte, rating: perfRating('TTFB', firstByte), at: firstByte }; + } + return { + pageId, + ...(typeof location === 'undefined' + ? {} + : { url: redactUrl(location.pathname + location.search) }), + ...(typeof document === 'undefined' ? {} : { title: redactMessage(document.title) }), + metrics, + interactions: [...interactions.values()] + .sort((a, b) => b.durationMs - a.durationMs) + .slice(0, MAX_INTERACTIONS), + longTasks: [...longTasks], + longTaskCount, + totalBlockingMs: round(totalBlockingMs), + supported: [...supported], + }; + }; + + const push = async () => { + if (left) return; + const next = report(); + const json = JSON.stringify(next); + if (json === lastJson && !keepaliveDue(sentAt, tickMs(), now())) return; + lastJson = json; + sentAt = now(); + try { + await my.rpc.call('push-performance', next); + } catch (error) { + lastJson = ''; + throw error; + } + }; + + return { + push, + /** The report as it would go out, for tests. */ + report, + leave() { + if (left) return; + left = true; + lastJson = ''; + void my.rpc.call('forget-performance-page', pageId).catch(() => {}); + }, + resume() { + left = false; + lastJson = ''; + }, + stop() { + for (const stop of stops.splice(0)) stop(); + }, + }; +} diff --git a/packages/devtools/src/rpc/build-register.ts b/packages/devtools/src/rpc/build-register.ts new file mode 100644 index 00000000..c7842e92 --- /dev/null +++ b/packages/devtools/src/rpc/build-register.ts @@ -0,0 +1,169 @@ +import { readBuildGraph, type BuildGraph } from '../build-graph.ts'; +import { redactUrl } from '../router.ts'; +import { code } from './forms-tools.ts'; + +/** `1234` as `1.2 kB`. */ +export function formatBytes(bytes: number): string { + if (bytes < 1000) return `${bytes} B`; + if (bytes < 1_000_000) return `${(bytes / 1000).toFixed(1)} kB`; + return `${(bytes / 1_000_000).toFixed(2)} MB`; +} + +const limitOf = (value: unknown, fallback: number, max: number) => + typeof value === 'number' && Number.isFinite(value) + ? Math.max(1, Math.min(max, Math.round(value))) + : fallback; + +/** URLs and paths can carry query secrets; they are masked like router URLs. */ +function masked(graph: BuildGraph): BuildGraph { + if (!graph.vite) return graph; + const vite = graph.vite; + return { + ...graph, + vite: { + ...vite, + modules: vite.modules.map((mod) => ({ + ...mod, + url: redactUrl(mod.url), + dynamicImports: mod.dynamicImports.map((url) => redactUrl(url)), + })), + entries: vite.entries.map((url) => redactUrl(url)), + lazy: vite.lazy.map((boundary) => ({ ...boundary, url: redactUrl(boundary.url) })), + }, + }; +} + +/** The `module-graph` answer. */ +export function moduleGraphText( + graph: BuildGraph, + args: { search?: unknown; limit?: unknown } = {}, +): string { + const vite = graph.vite; + if (!vite) return graph.viteNote ?? 'No module graph.'; + const limit = limitOf(args.limit, 20, 200); + const search = typeof args.search === 'string' ? args.search.trim().toLowerCase() : ''; + const lines = [ + `Vite dev server module graph: ${vite.modules.length} modules, ${formatBytes(vite.totalSize)} transformed (unminified, only modules the browser requested). Entries: ${vite.entries.map(code).join(', ') || 'none'}; ${formatBytes(vite.initialSize)} reachable from them without import().`, + ]; + if (search) { + const hits = vite.modules.filter((mod) => + `${mod.url} ${mod.file ?? ''}`.toLowerCase().includes(search), + ); + lines.push('', `Modules matching ${code(search)}: ${hits.length}.`); + if (hits.length) { + lines.push( + '', + '| Module | Size | Importers | Imports | import() |', + '| --- | --- | --- | --- | --- |', + ); + for (const mod of hits.slice(0, limit)) { + lines.push( + `| ${code(mod.file ?? mod.url)} | ${formatBytes(mod.size)} | ${mod.importers} | ${mod.imports} | ${mod.dynamicImports.map(code).join(', ')} |`, + ); + } + } + return lines.join('\n'); + } + lines.push('', '### Lazy boundaries (import())', ''); + if (!vite.lazy.length) { + lines.push('No module is loaded with import() yet, or no lazy route was visited.'); + } else { + lines.push('| Module | Imported by | Own modules | Own size |', '| --- | --- | --- | --- |'); + for (const boundary of vite.lazy.slice(0, limit)) { + lines.push( + `| ${code(boundary.file ?? boundary.url)} | ${boundary.importers.map(code).join(', ')} | ${boundary.modules} | ${formatBytes(boundary.size)} |`, + ); + } + } + lines.push('', '### Biggest modules', '', '| Module | Size | Importers |', '| --- | --- | --- |'); + for (const mod of vite.modules.slice(0, limit)) { + lines.push(`| ${code(mod.file ?? mod.url)} | ${formatBytes(mod.size)} | ${mod.importers} |`); + } + return lines.join('\n'); +} + +/** The `chunk-sizes` answer. */ +export function chunkSizesText(graph: BuildGraph, args: { limit?: unknown } = {}): string { + const output = graph.output; + if (!output) return graph.outputNote ?? 'No build output.'; + const limit = limitOf(args.limit, 30, 500); + const lines = [ + `Production build in ${code(output.dir)}, built ${new Date(output.builtAt).toISOString()}: initial ${formatBytes(output.initialBytes)} (${formatBytes(output.initialGzip)} gzip), lazy ${formatBytes(output.lazyBytes)} across ${output.chunks.filter((c) => !c.initial).length} chunks. Initial chunks are the ones index.html loads up front.`, + '', + '| Chunk | Initial | Size | Gzip | Entry point |', + '| --- | --- | --- | --- | --- |', + ]; + for (const chunk of output.chunks.slice(0, limit)) { + lines.push( + `| ${code(chunk.file)} | ${chunk.initial ? 'yes' : 'lazy'} | ${formatBytes(chunk.bytes)} | ${formatBytes(chunk.gzip)} | ${chunk.entryPoint ? code(chunk.entryPoint) : ''} |`, + ); + } + if (output.chunks.length > limit) { + lines.push('', `${output.chunks.length - limit} more; raise \`limit\` to see them.`); + } + if (!output.stats) { + lines.push( + '', + 'No stats.json next to the output, so chunks are not linked to their source. Build with `ng build --stats-json` to see the entry point and biggest inputs of each chunk.', + ); + } + return lines.join('\n'); +} + +/** devframe's RPC and agent definitions, as loose as the other inspectors take them. */ +// oxlint-disable-next-line typescript/no-explicit-any +type Definition = Record; + +export interface BuildHost { + register(definition: Definition): void; + agent: { registerTool(tool: Definition): void }; + /** The project root. */ + root: string; + /** The browser build folder, when the hub was given `browserDistFolder`. */ + outputDir?: string; +} + +/** + * The Build inspector on the server: the `get-build-graph` RPC function and + * the `module-graph` and `chunk-sizes` agent tools. Nothing is read until + * one of them is called. + */ +export function registerBuild(host: BuildHost): void { + const read = () => masked(readBuildGraph(host.root, { outputDir: host.outputDir })); + + host.register({ + name: 'get-build-graph', + type: 'query', + jsonSerializable: true, + handler: () => read(), + }); + + host.agent.registerTool({ + id: 'pangular:module-graph', + title: 'Show the module graph', + description: + "The Vite dev server's module graph: lazy boundaries (import()) with the modules and size each pulls in, and the biggest modules. `search` finds modules with their importers and imports. Sizes are transformed, unminified. Needs the Vite plugin.", + inputSchema: { + type: 'object', + properties: { + search: { type: 'string', description: 'Only modules whose URL or file contains this.' }, + limit: { type: 'integer', description: 'Rows per table (1-200, default 20).' }, + }, + }, + handler: async (args: Definition) => ({ markdown: moduleGraphText(read(), args ?? {}) }), + }); + + host.agent.registerTool({ + id: 'pangular:chunk-sizes', + title: 'Show chunk sizes', + description: + 'Chunks of the last production build on disk: initial or lazy (from index.html), size and gzip size, and with stats.json the entry point of each. Empty until the app was built.', + inputSchema: { + type: 'object', + properties: { + limit: { type: 'integer', description: 'Chunks to list (1-500, default 30).' }, + }, + }, + handler: async (args: Definition) => ({ markdown: chunkSizesText(read(), args ?? {}) }), + }); +} diff --git a/packages/devtools/src/rpc/perf-register.ts b/packages/devtools/src/rpc/perf-register.ts new file mode 100644 index 00000000..c0419bf7 --- /dev/null +++ b/packages/devtools/src/rpc/perf-register.ts @@ -0,0 +1,361 @@ +import { createSharedState } from 'devframe/utils/shared-state'; +import { + MAX_INTERACTIONS, + MAX_LONG_TASKS, + PERF_THRESHOLDS, + perfRating, + type PerfInteraction, + type PerfLongTask, + type PerfMetric, + type PerfMetricName, + type PerfOwner, + type PerfReport, +} from '../perf-collector.ts'; +import { redactMessage, redactUrl } from '../router.ts'; +import { defaultPage } from './default-page.ts'; +import { code, untrusted } from './forms-tools.ts'; +import { PAGE_ARGUMENT, byRecency, pageArgument, unknownPageText } from './pages.ts'; +import { trackPageSessions } from './page-sessions.ts'; +import type { PageTtl } from './page-ttl.ts'; + +export interface PerfPage extends PerfReport { + reportedAt: number; +} + +/** The `performance` shared state. */ +export interface PerfState { + pages: Record; +} + +const METRICS = Object.keys(PERF_THRESHOLDS) as PerfMetricName[]; +const MAX_MS = 24 * 3600_000; +const ENTRY_TYPES = [ + 'largest-contentful-paint', + 'paint', + 'layout-shift', + 'event', + 'longtask', + 'long-animation-frame', +]; + +type Loose = Record; + +const obj = (value: unknown): Loose => + value && typeof value === 'object' && !Array.isArray(value) ? (value as Loose) : {}; +const ms = (value: unknown): number | undefined => + typeof value === 'number' && Number.isFinite(value) && value >= 0 && value < MAX_MS + ? Math.round(value * 10) / 10 + : undefined; +const text = (value: unknown, max: number): string | undefined => + typeof value === 'string' && value + ? redactMessage(value.slice(0, max * 2)).slice(0, max) + : undefined; + +function owner(value: unknown): PerfOwner | undefined { + const o = obj(value); + const id = typeof o['id'] === 'string' && o['id'].length < 60 ? o['id'] : undefined; + const name = text(o['name'], 200); + return id && name ? { id, name } : undefined; +} + +function metric(name: PerfMetricName, value: unknown): PerfMetric | undefined { + const m = obj(value); + const raw = m['value']; + const number = + name === 'CLS' + ? typeof raw === 'number' && Number.isFinite(raw) && raw >= 0 && raw < 1000 + ? Math.round(raw * 10_000) / 10_000 + : undefined + : ms(raw); + if (number === undefined) return undefined; + const target = text(m['target'], 120); + const who = owner(m['owner']); + const event = text(m['event'], 40); + const at = ms(m['at']); + return { + value: number, + // Rated here, so a page can't claim a poor value is good. + rating: perfRating(name, number), + ...(target ? { target } : {}), + ...(who ? { owner: who } : {}), + ...(event ? { event } : {}), + ...(at !== undefined ? { at } : {}), + }; +} + +/** Reads a `push-performance` report; `null` when it is not one. */ +export function toPerfPage(message: unknown, now = Date.now()): PerfPage | null { + const report = obj(message); + const pageId = report['pageId']; + if (typeof pageId !== 'string' || !pageId || pageId.length >= 50) return null; + const metrics: PerfReport['metrics'] = {}; + const rawMetrics = obj(report['metrics']); + for (const name of METRICS) { + const value = metric(name, rawMetrics[name]); + if (value) metrics[name] = value; + } + const interactions: PerfInteraction[] = ( + Array.isArray(report['interactions']) ? report['interactions'] : [] + ) + .slice(0, MAX_INTERACTIONS) + .flatMap((item: unknown) => { + const i = obj(item); + const durationMs = ms(i['durationMs']); + const at = ms(i['at']); + if (durationMs === undefined || at === undefined) return []; + const target = text(i['target'], 120); + const who = owner(i['owner']); + return [ + { + event: text(i['event'], 40) ?? 'event', + durationMs, + at, + ...(target ? { target } : {}), + ...(who ? { owner: who } : {}), + }, + ]; + }); + const longTasks: PerfLongTask[] = (Array.isArray(report['longTasks']) ? report['longTasks'] : []) + .slice(-MAX_LONG_TASKS) + .flatMap((item: unknown) => { + const t = obj(item); + const durationMs = ms(t['durationMs']); + const at = ms(t['at']); + if (durationMs === undefined || at === undefined) return []; + const source = + typeof t['source'] === 'string' && t['source'] + ? redactUrl(t['source'].slice(0, 300)) + : undefined; + return [ + { + at, + durationMs, + kind: t['kind'] === 'long-animation-frame' ? 'long-animation-frame' : 'longtask', + ...(source ? { source } : {}), + }, + ]; + }); + const count = report['longTaskCount']; + const url = typeof report['url'] === 'string' ? redactUrl(report['url'].slice(0, 2000)) : ''; + const title = text(report['title'], 200); + return { + pageId, + ...(url ? { url } : {}), + ...(title ? { title } : {}), + metrics, + interactions, + longTasks, + longTaskCount: + typeof count === 'number' && Number.isInteger(count) && count >= 0 + ? Math.min(count, 1_000_000) + : longTasks.length, + totalBlockingMs: ms(report['totalBlockingMs']) ?? 0, + supported: (Array.isArray(report['supported']) ? report['supported'] : []).filter( + (type: unknown): type is string => typeof type === 'string' && ENTRY_TYPES.includes(type), + ), + reportedAt: now, + }; +} + +/** Pages with no report or heartbeat for longer than their TTL. */ +export function stalePerfPages(state: PerfState, now: number, ttl: PageTtl): string[] { + return Object.values(state.pages) + .filter((page) => now - page.reportedAt > ttl(page.pageId)) + .map((page) => page.pageId); +} + +const LABELS: Record = { + LCP: 'Largest Contentful Paint', + CLS: 'Cumulative Layout Shift', + INP: 'Interaction to Next Paint', + FCP: 'First Contentful Paint', + TTFB: 'Time to First Byte', +}; + +export function formatMetric(name: PerfMetricName, value: number): string { + if (name === 'CLS') return value.toFixed(3); + return value >= 1000 ? `${(value / 1000).toFixed(2)} s` : `${Math.round(value)} ms`; +} + +const RATING_TEXT = { good: 'good', 'needs-improvement': 'needs improvement', poor: 'poor' }; + +/** The `web-vitals` answer. */ +export function webVitalsText(state: PerfState, args: { page?: string } = {}): string { + const pages = byRecency(Object.values(state.pages)); + if (!pages.length) { + return 'No page reports web vitals. Open the app with the overlay loaded, in a browser that has PerformanceObserver (LCP, CLS and INP need a Chromium-based browser).'; + } + const page = args.page ? pages.find((p) => p.pageId === args.page) : defaultPage(pages); + if (!page) return unknownPageText(args.page!, pages, 'web vitals'); + const lines = [ + `Web vitals of page ${code(page.pageId)}${page.url ? ` (${code(page.url)})` : ''}, measured by the browser since the page loaded. Ratings use the web.dev thresholds.`, + '', + '| Metric | Value | Rating | Where |', + '| --- | --- | --- | --- |', + ]; + for (const name of METRICS) { + const m = page.metrics[name]; + if (!m) { + lines.push(`| ${LABELS[name]} (${name}) | none yet | | |`); + continue; + } + const where = [ + m.event ? code(m.event) : '', + m.target ? code(m.target) : '', + m.owner ? `in ${code(m.owner.name)} (${code(m.owner.id)})` : '', + ] + .filter(Boolean) + .join(' '); + lines.push( + `| ${LABELS[name]} (${name}) | ${formatMetric(name, m.value)} | ${RATING_TEXT[m.rating]} | ${where} |`, + ); + } + lines.push( + '', + `Long tasks: ${page.longTaskCount}, total blocking time ${formatMetric('TTFB', page.totalBlockingMs)}.`, + ); + if (page.interactions.length) { + lines.push( + '', + '### Slowest interactions', + '', + '| Event | Duration | At | Target |', + '| --- | --- | --- | --- |', + ); + for (const i of page.interactions) { + lines.push( + `| ${code(i.event)} | ${Math.round(i.durationMs)} ms | ${formatMetric('TTFB', i.at)} | ${i.target ? code(i.target) : ''}${i.owner ? ` in ${code(i.owner.name)}` : ''} |`, + ); + } + } + if (page.longTasks.length) { + lines.push( + '', + '### Latest long tasks', + '', + '| At | Duration | Source |', + '| --- | --- | --- |', + ); + for (const t of page.longTasks.slice(-10).reverse()) { + lines.push( + `| ${formatMetric('TTFB', t.at)} | ${Math.round(t.durationMs)} ms | ${t.source ? code(t.source) : t.kind} |`, + ); + } + } + const missing = ['largest-contentful-paint', 'layout-shift', 'event'].filter( + (type) => !page.supported.includes(type), + ); + if (missing.length) { + lines.push( + '', + `This browser does not observe ${missing.map(code).join(', ')}, so the matching metrics stay empty.`, + ); + } + if (pages.length > 1 && !args.page) { + lines.push( + '', + `Other pages report web vitals too: ${pages + .slice(1) + .map((p) => code(p.pageId)) + .join(', ')}. Pass one as \`page\`.`, + ); + } + return untrusted(lines.join('\n')); +} + +interface Scoped { + rpc: { sharedState(name: string, options: Loose): Promise }; +} + +/** devframe's RPC and agent definitions, as loose as the other inspectors take them. */ +// oxlint-disable-next-line typescript/no-explicit-any +type Definition = Record; + +interface PerfShared { + value(): PerfState; + mutate(recipe: (draft: PerfState) => void): void; +} + +export interface PerfHost { + my: Scoped; + rpcHost: unknown; + register(definition: Definition): void; + agent: { registerTool(tool: Definition): void }; + owns(pageId: unknown): pageId is string; + mayForget(pageId: unknown): pageId is string; + ttl: PageTtl; +} + +export interface PerfRegistration { + pages(): { pageId: string; reportedAt: number; url?: string; title?: string }[]; + expire(now?: number): void; + state(): PerfState; +} + +/** + * The Performance inspector on the server: the `performance` shared state, + * the `push-performance` and `forget-performance-page` RPC functions and the + * `web-vitals` agent tool. + */ +export async function registerPerformance(host: PerfHost): Promise { + const shared = (await host.my.rpc.sharedState('performance', { + sharedState: createSharedState({ initialValue: { pages: {} }, enablePatches: true }), + })) as unknown as PerfShared; + const state = () => shared.value(); + const drop = (pageIds: readonly string[]) => { + const present = pageIds.filter((id) => state().pages[id]); + if (!present.length) return; + shared.mutate((draft) => { + for (const id of present) delete draft.pages[id]; + }); + }; + const sessions = trackPageSessions(host.rpcHost, drop); + + host.register({ + name: 'push-performance', + type: 'action', + jsonSerializable: true, + handler: (message: unknown) => { + const page = toPerfPage(message); + if (!page || !host.owns(page.pageId)) return; + sessions.bind(page.pageId); + shared.mutate((draft) => { + draft.pages[page.pageId] = page; + }); + }, + }); + + host.register({ + name: 'forget-performance-page', + type: 'action', + jsonSerializable: true, + handler: (pageId: unknown) => { + if (!host.mayForget(pageId)) return; + sessions.unbind(pageId); + drop([pageId]); + }, + }); + + host.agent.registerTool({ + id: 'pangular:web-vitals', + title: 'Show web vitals', + description: + "The page's web vitals as the browser measured them: LCP with its element and component, CLS, INP with the slowest interactions, FCP, TTFB, long tasks and total blocking time, each rated by the web.dev thresholds. Empty without a page.", + inputSchema: { type: 'object', properties: { page: PAGE_ARGUMENT } }, + handler: async (args: Definition) => ({ + markdown: webVitalsText(state(), { page: pageArgument(args) }), + }), + }); + + return { + pages: () => + Object.values(state().pages).map((page) => ({ + pageId: page.pageId, + reportedAt: page.reportedAt, + url: page.url, + title: page.title, + })), + state, + expire: (now = Date.now()) => drop(stalePerfPages(state(), now, host.ttl)), + }; +} diff --git a/packages/devtools/src/vite.ts b/packages/devtools/src/vite.ts index 3b69102d..741b8d1c 100644 --- a/packages/devtools/src/vite.ts +++ b/packages/devtools/src/vite.ts @@ -29,6 +29,7 @@ import { overlayTags } from './vite-overlay.ts'; import { signalPrimitivesAlias } from './vite-signals-stub.ts'; import { terminalsEnabled } from './hub-terminals.ts'; import { watchViteErrors, type ViteErrorSource } from './server-errors.ts'; +import { buildRegistry, viteGraphSource } from './build-graph.ts'; export type { PangularConfig } from './config.ts'; import type { PangularServerPlugin } from './plugin.ts'; @@ -200,12 +201,18 @@ export default function pangularVite(options: PangularViteOptions = {}): Plugin if (url) logger.info(` \u279C Pangular Inspector: ${url}`); }; } + // The module graph for the Build tab, read only when the panel or an agent asks. + const graph = inspectors.build + ? viteGraphSource(server as unknown as Parameters[0]) + : undefined; + if (graph) buildRegistry().vite = graph; // Transform and compile errors Vite shows in its overlay, for the Errors tab. const stopViteErrors = inspectors.errors ? watchViteErrors(server as unknown as ViteErrorSource) : () => {}; httpServer?.once('close', () => { stopViteErrors(); + if (graph && buildRegistry().vite === graph) buildRegistry().vite = undefined; void devtools.context .then(releaseServerState, () => undefined) .finally(() => devtools.close()); From 34d8cb180a6af56cc46208a01d38c0d603c2ac50 Mon Sep 17 00:00:00 2001 From: erKam Date: Sat, 7 Nov 2026 18:51:47 +0000 Subject: [PATCH 07/32] fix: say Loading on the Build tab and keep its data in static reports The axe harness waits for a Loading state on every tab, and the Build tab said Reading. get-build-graph is now a snapshot query, so a static report keeps the chunks of the build next to it instead of an error. --- app/src/pages/build-inspector.ts | 4 ++-- packages/devtools/src/rpc/build-register.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/pages/build-inspector.ts b/app/src/pages/build-inspector.ts index d81353cf..c6ec05c5 100644 --- a/app/src/pages/build-inspector.ts +++ b/app/src/pages/build-inspector.ts @@ -70,7 +70,7 @@ export const BUILD_ROWS = 25;

{{ status() }}

@@ -235,7 +235,7 @@ export const BUILD_ROWS = 25; } } @else { -

Reading the build…

+

Loading the build…

} `, styles: ` diff --git a/packages/devtools/src/rpc/build-register.ts b/packages/devtools/src/rpc/build-register.ts index c7842e92..dcee3e21 100644 --- a/packages/devtools/src/rpc/build-register.ts +++ b/packages/devtools/src/rpc/build-register.ts @@ -135,6 +135,9 @@ export function registerBuild(host: BuildHost): void { name: 'get-build-graph', type: 'query', jsonSerializable: true, + // A static report keeps the chunks of the build it was made next to. + snapshot: true, + args: [], handler: () => read(), }); From 5a3892220df0cda5879a70cc830c401670d1430f Mon Sep 17 00:00:00 2001 From: erKam Date: Sat, 7 Nov 2026 18:54:11 +0000 Subject: [PATCH 08/32] ci: hold source files over 1,500 lines at their length pnpm lint counts the lines of each package and panel source file over 1,500 as pangular(max-lines) in the same ratchet, so a long file can't grow and a new file can't pass the limit. The baseline holds today's twelve long files. Refs #373 --- AGENTS.md | 24 ++++----- .../src/content/contributing/development.md | 2 +- lint-baseline.json | 32 ++++++++++++ packages/devtools/CHANGELOG.md | 2 +- .../src/__tests__/lint-ratchet.test.ts | 35 ++++++++++++- scripts/lint-ratchet.d.mts | 9 +++- scripts/lint-ratchet.mjs | 49 +++++++++++++++++-- 7 files changed, 132 insertions(+), 21 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7fd60e90..96466a04 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,18 +14,18 @@ Pangular Inspector inspects a running Angular app and serves what it finds to a ### Commands -| Command | What it runs | -| ---------------------- | --------------------------------------------------------------------------- | -| `pnpm test:devtools` | Package tests | -| `pnpm test:panel` | Panel tests | -| `pnpm test:axe` | Builds a static report and runs axe on every panel view | -| `pnpm typecheck` | TypeScript and template checks for every project | -| `pnpm format:check` | Prettier, including the docs markdown | -| `pnpm lint` | oxlint with a ratchet: no file gets more warnings than `lint-baseline.json` | -| `pnpm skills:check` | Checks the skills and roles in `.claude` and the skills the package ships | -| `pnpm docs:build` | Builds the docs site with its link guards | -| `pnpm extension:build` | Rebuilds `extension/ui` from `app` | -| `pnpm commit:check` | Checks the commit messages on the branch | +| Command | What it runs | +| ---------------------- | --------------------------------------------------------------------------------------------------------------- | +| `pnpm test:devtools` | Package tests | +| `pnpm test:panel` | Panel tests | +| `pnpm test:axe` | Builds a static report and runs axe on every panel view | +| `pnpm typecheck` | TypeScript and template checks for every project | +| `pnpm format:check` | Prettier, including the docs markdown | +| `pnpm lint` | oxlint with a ratchet: no file gets more warnings than `lint-baseline.json`, and no file over 1,500 lines grows | +| `pnpm skills:check` | Checks the skills and roles in `.claude` and the skills the package ships | +| `pnpm docs:build` | Builds the docs site with its link guards | +| `pnpm extension:build` | Rebuilds `extension/ui` from `app` | +| `pnpm commit:check` | Checks the commit messages on the branch | The words this project uses for its own concepts (overlay, collector, hub, devframe, agent tool and so on) are defined in `docs/CONTEXT.md`. Use them the same way in code, docs and issues. diff --git a/apps/docs/src/content/contributing/development.md b/apps/docs/src/content/contributing/development.md index ec4251cc..81df9841 100644 --- a/apps/docs/src/content/contributing/development.md +++ b/apps/docs/src/content/contributing/development.md @@ -169,7 +169,7 @@ The mobile overlays (NativeScript, Angular Native and the Capacitor collector) h `pnpm test:coverage` runs `pnpm test:devtools --coverage`, then `pnpm test:panel --coverage`, each with `--testTimeout=20000`: v8 instrumentation slows every test, and the slowest ones went over the default 5 seconds. The v8 provider writes an HTML report, `coverage-summary.json` and `lcov.info` to `dist/coverage/devtools` and `dist/coverage/panel`, also when a test fails. There is no threshold: the report shows what the tests reach and nothing fails on a low number. Open `dist/coverage/devtools/index.html` to read it. -`pnpm lint` runs oxlint with `.oxlintrc.json` (the correctness rules plus `typescript/no-explicit-any`) and compares the warnings of each file and rule with `lint-baseline.json`. A file with more warnings of a rule than the baseline fails the check, also a new file. The baseline only goes down: after you fix warnings, `pnpm lint --update` lowers it, and it refuses to raise a count unless you pass `--allow-new`. +`pnpm lint` runs oxlint with `.oxlintrc.json` (the correctness rules plus `typescript/no-explicit-any`) and compares the warnings of each file and rule with `lint-baseline.json`. A file with more warnings of a rule than the baseline fails the check, also a new file. The baseline only goes down: after you fix warnings, `pnpm lint --update` lowers it, and it refuses to raise a count unless you pass `--allow-new`. The same ratchet keeps source files short: a `.ts` file in `packages/devtools/src` or `app/src` (tests aside) over 1,500 lines counts its lines as `pangular(max-lines)`, so it can't grow, and a new file can't pass the limit. Split a long file instead of adding to it. `pnpm skills:check` validates the frontmatter of every skill and role and checks that the files and links they mention exist. `pnpm commit:check` checks every commit on your branch that is not on `main` (it compares with `upstream/main`, then `origin/main`, then `main`). diff --git a/lint-baseline.json b/lint-baseline.json index 696cbf87..07e16531 100644 --- a/lint-baseline.json +++ b/lint-baseline.json @@ -11,6 +11,33 @@ "app/src/__tests__/virtual-window.test.ts": { "eslint(no-setter-return)": 1 }, + "app/src/app.ts": { + "pangular(max-lines)": 1912 + }, + "app/src/pages/analog-inspector.ts": { + "pangular(max-lines)": 2648 + }, + "app/src/pages/component-tree.ts": { + "pangular(max-lines)": 2750 + }, + "app/src/pages/di-inspector.ts": { + "pangular(max-lines)": 2235 + }, + "app/src/pages/forms-inspector.ts": { + "pangular(max-lines)": 1791 + }, + "app/src/pages/network-inspector.ts": { + "pangular(max-lines)": 2923 + }, + "app/src/pages/pipes-inspector.ts": { + "pangular(max-lines)": 1545 + }, + "app/src/pages/signal-inspector.ts": { + "pangular(max-lines)": 2341 + }, + "app/src/pages/store-inspector.ts": { + "pangular(max-lines)": 2449 + }, "app/src/pages/store-types.ts": { "typescript(no-explicit-any)": 1 }, @@ -207,10 +234,14 @@ "packages/devtools/src/component-screenshot.ts": { "unicorn(prefer-string-starts-ends-with)": 1 }, + "packages/devtools/src/config.ts": { + "pangular(max-lines)": 2390 + }, "packages/devtools/src/console-tap.ts": { "unicorn(no-useless-spread)": 1 }, "packages/devtools/src/devframe.ts": { + "pangular(max-lines)": 4876, "unicorn(no-useless-spread)": 1 }, "packages/devtools/src/dom-diff.ts": { @@ -258,6 +289,7 @@ "unicorn(no-useless-spread)": 2 }, "packages/devtools/src/ngrx-collector.ts": { + "pangular(max-lines)": 1618, "typescript(no-explicit-any)": 1, "unicorn(no-useless-fallback-in-spread)": 1, "unicorn(no-useless-spread)": 3 diff --git a/packages/devtools/CHANGELOG.md b/packages/devtools/CHANGELOG.md index 2b2908be..c27e1890 100644 --- a/packages/devtools/CHANGELOG.md +++ b/packages/devtools/CHANGELOG.md @@ -244,7 +244,7 @@ What remains: `/config` exports `DEFAULT_POPUP_HOTKEY`, `PANGULAR_ACTIONS`, `PAN ### Release and CI - The overlay reads `window.ng` through one typed `NgDebugApi` (`debug-api.ts`) with the Angular version of each function, and a test against a real app fails when Angular publishes a function the type doesn't declare (#373). -- `pnpm lint` runs oxlint with a ratchet: CI fails when a file gets more warnings of a rule than `lint-baseline.json` allows (#373). +- `pnpm lint` runs oxlint with a ratchet: CI fails when a file gets more warnings of a rule than `lint-baseline.json` allows, or when a source file over 1,500 lines grows (#373). - The `leaks-real` test expects the rows the collector really reports (#430). - Pull requests run the security and MCP end-to-end checks, and a weekly job runs the rest. - A CI job installs the packed package on Node.js 22.12.0, the `engines.node` floor, and loads its CLI and its `cli`, `hub`, `ssr` and `vite` entry points there (#373). diff --git a/packages/devtools/src/__tests__/lint-ratchet.test.ts b/packages/devtools/src/__tests__/lint-ratchet.test.ts index 3d9ca23a..b60092a1 100644 --- a/packages/devtools/src/__tests__/lint-ratchet.test.ts +++ b/packages/devtools/src/__tests__/lint-ratchet.test.ts @@ -1,5 +1,10 @@ import { describe, expect, it } from 'vitest'; -import { compareCounts, countDiagnostics } from '../../../../scripts/lint-ratchet.mjs'; +import { + MAX_LINES_RULE, + compareCounts, + countDiagnostics, + lineDiagnostics, +} from '../../../../scripts/lint-ratchet.mjs'; describe('lint ratchet', () => { it('counts diagnostics per file and rule with sorted keys', () => { @@ -41,4 +46,32 @@ describe('lint ratchet', () => { fixed: ['a.ts: any 2 -> 1', 'b.ts: unused 1 -> 0'], }); }); + + it('holds files over the line limit at their length, and lets short ones be', () => { + const lines = lineDiagnostics( + [ + { file: 'big.ts', lines: 1600 }, + { file: 'small.ts', lines: 200 }, + ], + 1500, + ); + expect(lines.map((d) => [d.filename, d.count])).toEqual([['big.ts', 1600]]); + const baseline = countDiagnostics(lines, '/repo'); + expect(baseline).toEqual({ 'big.ts': { [MAX_LINES_RULE]: 1600 } }); + const grown = countDiagnostics( + lineDiagnostics([{ file: 'big.ts', lines: 1610 }], 1500), + '/repo', + ); + expect(compareCounts(baseline, grown).added).toEqual([ + `big.ts: ${MAX_LINES_RULE} 1600 -> 1610`, + ]); + const split = countDiagnostics( + lineDiagnostics([{ file: 'big.ts', lines: 900 }], 1500), + '/repo', + ); + expect(compareCounts(baseline, split)).toEqual({ + added: [], + fixed: [`big.ts: ${MAX_LINES_RULE} 1600 -> 0`], + }); + }); }); diff --git a/scripts/lint-ratchet.d.mts b/scripts/lint-ratchet.d.mts index fb096013..35ca7af3 100644 --- a/scripts/lint-ratchet.d.mts +++ b/scripts/lint-ratchet.d.mts @@ -1,6 +1,13 @@ export declare const LINT_PATHS: string[]; +export declare const MAX_LINES: number; +export declare const LINE_PATHS: string[]; +export declare const MAX_LINES_RULE: string; +export declare function lineDiagnostics( + files: { file: string; lines: number }[], + max?: number, +): { filename: string; code: string; count: number; message: string }[]; export declare function countDiagnostics( - diagnostics: { filename: string; code?: string }[], + diagnostics: { filename: string; code?: string; count?: number }[], root?: string, ): Record>; export declare function compareCounts( diff --git a/scripts/lint-ratchet.mjs b/scripts/lint-ratchet.mjs index a4fca503..78919eba 100644 --- a/scripts/lint-ratchet.mjs +++ b/scripts/lint-ratchet.mjs @@ -2,8 +2,10 @@ // Runs oxlint and fails when a file has more warnings of a rule than // `lint-baseline.json` allows. The baseline only goes down: fix a warning and // run `pnpm lint --update` to lower it, so the old count can't come back. +// The same ratchet keeps source files short: a file over MAX_LINES counts its +// lines as `pangular(max-lines)`, so it can't grow and a new one can't start. import { execFileSync } from 'node:child_process'; -import { readFileSync, realpathSync, writeFileSync } from 'node:fs'; +import { readdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'; import { dirname, join, relative, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -11,22 +13,59 @@ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); const BASELINE = join(ROOT, 'lint-baseline.json'); /** The folders oxlint checks, relative to the repository root. */ export const LINT_PATHS = ['packages', 'app', 'apps', 'src', 'examples', 'scripts', 'e2e']; +/** Lines a source file may have before the ratchet holds it. */ +export const MAX_LINES = 1500; +/** The folders whose source files the line limit covers; tests are left out. */ +export const LINE_PATHS = ['packages/devtools/src', 'app/src']; +export const MAX_LINES_RULE = 'pangular(max-lines)'; + +/** + * A pseudo diagnostic for each file over {@link MAX_LINES}, whose `count` is + * its line count, so the ratchet keeps it from growing. + * @param {{ file: string; lines: number }[]} files + * @returns {{ filename: string; code: string; count: number; message: string }[]} + */ +export function lineDiagnostics(files, max = MAX_LINES) { + return files + .filter((f) => f.lines > max) + .map((f) => ({ + filename: f.file, + code: MAX_LINES_RULE, + count: f.lines, + message: `${f.lines} lines, over the limit of ${max}. Split the file instead of adding to it.`, + })); +} + +function sourceFiles(dir) { + /** @type {{ file: string; lines: number }[]} */ + const out = []; + for (const entry of readdirSync(join(ROOT, dir), { withFileTypes: true })) { + const path = `${dir}/${entry.name}`; + if (entry.isDirectory()) { + if (entry.name === '__tests__' || entry.name === 'node_modules') continue; + out.push(...sourceFiles(path)); + } else if (entry.name.endsWith('.ts') && !/\.(test|spec|d)\.ts$/.test(entry.name)) { + out.push({ file: path, lines: readFileSync(join(ROOT, path), 'utf8').split('\n').length }); + } + } + return out; +} /** * Counts oxlint's diagnostics per file and rule, with `/` paths relative to * `root`, sorted so the baseline file diffs cleanly. - * @param {{ filename: string; code?: string }[]} diagnostics + * @param {{ filename: string; code?: string; count?: number }[]} diagnostics * @param {string} root * @returns {Record>} */ export function countDiagnostics(diagnostics, root = ROOT) { /** @type {Record>} */ const counts = {}; - for (const { filename, code } of diagnostics) { + for (const { filename, code, count } of diagnostics) { const file = relative(root, join(root, filename)).split(sep).join('/'); const rule = code ?? 'unknown'; const rules = (counts[file] ??= {}); - rules[rule] = (rules[rule] ?? 0) + 1; + rules[rule] = (rules[rule] ?? 0) + (count ?? 1); } return sortCounts(counts); } @@ -104,7 +143,7 @@ function readBaseline() { } function main(args) { - const diagnostics = runOxlint(); + const diagnostics = [...runOxlint(), ...lineDiagnostics(LINE_PATHS.flatMap(sourceFiles))]; const current = countDiagnostics(diagnostics); const baseline = readBaseline(); const { added, fixed } = compareCounts(baseline, current); From 36ba562d2a0be865ac8a364ba1d86c568f096a66 Mon Sep 17 00:00:00 2001 From: erKam Date: Sat, 7 Nov 2026 19:18:17 +0000 Subject: [PATCH 09/32] refactor(mcp): move devframe.ts agent tools into their own modules Moves four blocks of the devframe server's setup, unchanged, into devframe-component-tools.ts, devframe-router-tools.ts, devframe-forms-tools.ts and devframe-http-tools.ts. Each exports one register function that takes the setup values it reads as an env object, typed through devframe-env.ts. The forms block hands back the wait-for form condition it sets up. The tools, their order and their answers stay the same, and devframe.ts goes from 4,876 to 2,933 lines. Refs #373 --- .claude/skills/devtools-inspector/SKILL.md | 4 +- docs/CONTEXT.md | 2 +- lint-baseline.json | 2 +- .../devtools/src/devframe-component-tools.ts | 950 +++++++ packages/devtools/src/devframe-env.ts | 29 + packages/devtools/src/devframe-forms-tools.ts | 581 +++++ packages/devtools/src/devframe-http-tools.ts | 407 +++ .../devtools/src/devframe-router-tools.ts | 379 +++ packages/devtools/src/devframe.ts | 2192 +---------------- 9 files changed, 2476 insertions(+), 2070 deletions(-) create mode 100644 packages/devtools/src/devframe-component-tools.ts create mode 100644 packages/devtools/src/devframe-env.ts create mode 100644 packages/devtools/src/devframe-forms-tools.ts create mode 100644 packages/devtools/src/devframe-http-tools.ts create mode 100644 packages/devtools/src/devframe-router-tools.ts diff --git a/.claude/skills/devtools-inspector/SKILL.md b/.claude/skills/devtools-inspector/SKILL.md index 8b6fedd8..3b4f9412 100644 --- a/.claude/skills/devtools-inspector/SKILL.md +++ b/.claude/skills/devtools-inspector/SKILL.md @@ -32,6 +32,8 @@ Read `docs/contributing/coding-standards.md` ("Reading data from the page") befo ## Server side (`devframe.ts`, `rpc/`) +- `devframe.ts` wires the setup. A new inspector goes in its own `rpc/-register.ts` (see `rpc/errors-register.ts`), and agent tools of an existing area live in `devframe--tools.ts`, which take the setup values they read as an `env` object typed through `devframe-env.ts`. Keep `devframe.ts` under the line ratchet of `pnpm lint`. + - Keep a `Map` with `reportedAt`, drop pages older than `ttl(pageId)` (from `visibility.ttl()`, so a page with a current heartbeat never expires) in the shared expiry interval, and write the combined value into the shared state. - Page actions that the panel triggers (restore, highlight, run) go panel -> `request--action` -> broadcast `-action` to the page -> `-action-result`, keyed by `requestId`, with a timeout. - Source scans in `rpc/` enrich or stand in for live data. They must return a `kind` for anything the Dashboard counts. @@ -46,7 +48,7 @@ Read `docs/contributing/coding-standards.md` ("Reading data from the page") befo - Describe what the tool returns, where the data comes from and what an empty answer means. Answer in markdown. - Give each new tool or resource one entry in `AGENT_ENTRIES` (`packages/devtools/src/config.ts`): its inspector, safety tier, the `actions` it writes for and whether it needs a page. `AGENT_INSPECTOR`, `AGENT_SAFETY`, `ACTION_TOOLS`, `PAGE_AGENT_ENTRIES` and the tags derive from it. `packages/devtools/src/__tests__/agent-metadata.test.ts` fails on a registered tool without an entry; update `packages/devtools/src/__tests__/fixtures/agent-metadata-derived.json` with it. -- Update descriptions in `devframe.ts` when data shapes change, and the tool list on the docs site (apps/docs/src/content/agents/tools.md). Use the `devtools-docs` skill for that edit. +- Update tool descriptions (in `devframe.ts`, `devframe--tools.ts` or the inspector's register module) when data shapes change, and the tool list on the docs site (apps/docs/src/content/agents/tools.md). Use the `devtools-docs` skill for that edit. ## Tests diff --git a/docs/CONTEXT.md b/docs/CONTEXT.md index 8fdf045b..7919fba8 100644 --- a/docs/CONTEXT.md +++ b/docs/CONTEXT.md @@ -53,7 +53,7 @@ A library's own view in the panel and its own agent tools, through the public pl _Avoid_: extension (the Chrome extension), add-on, inspector **Devframe**: -The framework the devtools are built on (`devframe`). One definition, `packages/devtools/src/devframe.ts`, declares the RPC functions, shared state and agent tools, and Devframe serves it as the embedded panel, the standalone CLI, the static report and the MCP server. +The framework the devtools are built on (`devframe`). One definition, `packages/devtools/src/devframe.ts` (with the `devframe-*-tools.ts` and `rpc/*-register.ts` modules it calls), declares the RPC functions, shared state and agent tools, and Devframe serves it as the embedded panel, the standalone CLI, the static report and the MCP server. _Avoid_: framework, runtime, backend **Hub**: diff --git a/lint-baseline.json b/lint-baseline.json index 07e16531..83acb95c 100644 --- a/lint-baseline.json +++ b/lint-baseline.json @@ -241,7 +241,7 @@ "unicorn(no-useless-spread)": 1 }, "packages/devtools/src/devframe.ts": { - "pangular(max-lines)": 4876, + "pangular(max-lines)": 2934, "unicorn(no-useless-spread)": 1 }, "packages/devtools/src/dom-diff.ts": { diff --git a/packages/devtools/src/devframe-component-tools.ts b/packages/devtools/src/devframe-component-tools.ts new file mode 100644 index 00000000..accee26f --- /dev/null +++ b/packages/devtools/src/devframe-component-tools.ts @@ -0,0 +1,950 @@ +import { forBlocksText } from './rpc/for-blocks-tools.ts'; +import { + HYDRATION_DESCRIPTION, + hydrationOverlayText, + hydrationText, +} from './rpc/hydration-tools.ts'; +import { inspectProvidersText } from './rpc/injector-tools.ts'; +import { TOKEN_ID_ARGUMENT } from './rpc/token-match.ts'; +import { registerInjectionExplain } from './rpc/injection-explain.ts'; +import { templateLintTool } from './rpc/template-lint-tool.ts'; +import { SIGNAL_TOOL_MAX, signalGraphText, type InspectSignalsArgs } from './rpc/signal-tools.ts'; +import { noSelectorMatchText, signalPeekFor } from './rpc/signal-peeks.ts'; +import { injectorMatches, isEnvironmentRequest } from './signal-graph.ts'; +import type { + ComponentDetail, + ComponentPage, + InjectorPage, + LiveComponentNode, + SignalGraph, +} from './types.ts'; +import { zoneModeText } from './zone-mode.ts'; +import { + deferBlocksText, + directiveHostText, + otherMatchesText, + truncationText, +} from './rpc/component-tools.ts'; +import { LIST_COMPONENTS_DESCRIPTION, listComponentsText } from './rpc/component-outline.ts'; +import { inspectComponentText } from './rpc/component-detail-text.ts'; +import { valueResultText } from './rpc/component-values.ts'; +import { parsePath } from './component-edit.ts'; +import { UNTRUSTED, untrusted } from './rpc/forms-tools.ts'; +import { type RouterPage } from './rpc/router-tools.ts'; +import { + cdOptionsText, + cdUnknownPageText, + cdViewsAsked, + cdViewsText, + changeDetectionText, + pickCdPage, + type CdPage, + type CdState, +} from './rpc/cd-tools.ts'; +import { cdExportText } from './rpc/cd-export-tools.ts'; +import { cdDiagnosisText } from './rpc/cd-diagnosis-tools.ts'; +import { unsupportedToolText } from './rpc/angular-pages.ts'; +import { + PAGE_ARGUMENT, + byRecency, + pageArgument, + nativeAppName, + needsDomText, + unknownPageText, +} from './rpc/pages.ts'; +import { clip } from './text.ts'; +import { agentToolOffered, angularUnavailableText, type ResolvedPangularConfig } from './config.ts'; +import { defaultPage, rankPages } from './rpc/default-page.ts'; +import { cdRecordAnswer } from './rpc/cd-record-tool.ts'; +import { refusal, refusedAnswer } from './rpc/refusal.ts'; +import type { + AgentRegistrar, + DevframeNodeContext, + PageGraph, + PangularScope, + SharedState, +} from './devframe-env.ts'; + +import type { AngularPageInfo } from './config.ts'; +import type { AngularPage } from './rpc/angular-pages.ts'; +import type { AngularPagesState } from './rpc/angular-pages.ts'; +import type { PushAcks } from './rpc/push-acks.ts'; +import type { ComponentValueResult } from './component-values.ts'; +import type { ProviderValueResult } from './provider-values.ts'; +import type { SignalPeekTarget } from './signal-peek.ts'; + +const ENV_WAIT_MS = 1500; +const AGENT_HIGHLIGHT_MS = 2000; +/** What `registerComponentAgentTools` reads from the devframe server's setup (`devframe.ts`). */ +export interface RegisterComponentAgentToolsEnv { + agent: AgentRegistrar; + angularPages: { + set(report: AngularPageInfo & { pageId: string }): boolean; + forget(pageId: string): boolean; + expire(ttl: (pageId: string) => number): boolean; + get(pageId: string | undefined): AngularPage | undefined; + pick(pageId?: string): AngularPage | undefined; + snapshot(): AngularPagesState; + }; + cdAcks: PushAcks; + cdState: SharedState; + componentHits: ( + selector: string, + pageId?: string, + ) => { pageId: string; node: LiveComponentNode }[]; + componentPageProperty: { type: string; description: string }; + componentPages: Map; + componentValues: { + answer: (message: { requestId?: unknown; pageId?: unknown; result?: unknown } | null) => void; + request: (page: unknown, message: unknown) => Promise; + pendingCount: () => number; + }; + config: ResolvedPangularConfig; + ctx: DevframeNodeContext; + goneCdPages: Set; + injectorPages: Map; + latestInjectorPage: () => InjectorPage; + my: PangularScope; + noComponentTree: (outcome: string) => string; + providerValues: { + answer: (message: { requestId?: unknown; pageId?: unknown; result?: unknown } | null) => void; + request: (page: unknown, message: unknown) => Promise; + tool: { + id: string; + title: string; + description: string; + inputSchema: { + type: string; + properties: { + token: { type: string; description: string }; + tokenId: { + readonly type: 'string'; + readonly description: 'Optional. The `tokenId` of the token (`tok-3`), from the injector tree or from an answer that lists several tokens with the same name. Pass it when two tokens share a name, such as two `InjectionToken` with the description `config`.'; + }; + injector: { type: string; description: string }; + path: { type: string; description: string }; + depth: { type: string; description: string }; + limit: { type: string; description: string }; + page: { + readonly type: 'string'; + readonly description: 'Page id, when more than one tab reports (see pangular:list-pages). Defaults to its first row.'; + }; + }; + required: string[]; + }; + handler: (args: Record) => Promise<{ markdown: string }>; + }; + pendingCount: () => number; + }; + register: PangularScope['rpc']['register']; + requestCdOptions: (message: unknown) => void; + requestCdRecord: (message: { pageId?: unknown; on?: unknown; clear?: unknown }) => void; + requestHydrationOverlay: (message: { pageId?: unknown; on?: unknown } | null) => void; + routerPages: Map; + selectComponentOnPage: (pageId: string | undefined, id: string | null, console?: boolean) => void; + signalGraphState: SharedState<{ + graph: SignalGraph | null; + pages: Record; + selectedNodeId: string | null; + }>; + signalPages: Map; + signalPeeks: { + request: ( + send: (message: SignalPeekTarget & { requestId: string; pageId?: string }) => void, + target: SignalPeekTarget, + pageId: string | undefined, + pages: readonly string[], + timeoutMs: number, + ) => Promise; + answer: (message: unknown) => void; + }; + unknownComponentPage: (pageId: string | undefined) => { markdown: string } | null; + waitForComponentDetail: ( + pageId: string, + id: string, + timeoutMs?: number, + ) => Promise; +} + +export function registerComponentAgentTools(env: RegisterComponentAgentToolsEnv) { + const { + agent, + angularPages, + cdAcks, + cdState, + componentHits, + componentPageProperty, + componentPages, + componentValues, + config, + ctx, + goneCdPages, + injectorPages, + latestInjectorPage, + my, + noComponentTree, + providerValues, + register, + requestCdOptions, + requestCdRecord, + requestHydrationOverlay, + routerPages, + selectComponentOnPage, + signalGraphState, + signalPages, + signalPeeks, + unknownComponentPage, + waitForComponentDetail, + } = env; + agent.registerTool({ + id: 'pangular:highlight', + title: 'Highlight a component', + description: + 'Outline a component in the page and select it, so component-tree and inspect-signals follow it: `component` (instance id, class or tag; the first of several matches) or `selector` (CSS). `hydration: true` outlines the hydration regions instead, `false` removes them.', + inputSchema: { + type: 'object', + properties: { + component: { + type: 'string', + description: + 'Instance id (e.g. c12), class name (e.g. ProductCard) or host tag (e.g. app-root).', + }, + selector: { type: 'string', description: 'A CSS selector, such as .card > h2.' }, + hydration: { + type: 'boolean', + description: + 'Outline the hydration regions on the page (true), or remove the outline (false).', + }, + page: componentPageProperty, + }, + }, + handler: async (args: { + component?: string; + selector?: string; + hydration?: boolean; + page?: string; + }) => { + const page = pageArgument(args); + const all = byRecency(componentPages.values()); + if (page && !componentPages.has(page)) { + return { markdown: unknownPageText(page, all, 'a component tree') }; + } + const pages = page ? all.filter((entry) => entry.pageId === page) : all; + const given = (['component', 'selector', 'hydration'] as const).filter( + (key) => args[key] !== undefined, + ); + if (given.length !== 1) { + return { + markdown: refusal( + 'Pass one of `component` (an instance id, class name or host tag), `selector` (a CSS selector) or `hydration` (true or false).', + ), + }; + } + if (args.hydration !== undefined) { + const done = hydrationOverlayText(args.hydration, pages); + const sent = args.hydration ? pages.some((entry) => entry.hydration) : pages.length > 0; + // Nothing to send the request to is a refusal, not a request sent. + if (!sent) return { markdown: refusal(`${done}\n\n${hydrationText(pages)}`) }; + requestHydrationOverlay({ pageId: page, on: args.hydration }); + return { markdown: `${done}\n\n${hydrationText(pages)}` }; + } + if (!pages.some((entry) => entry.roots.length)) { + return refusedAnswer(noComponentTree(', so nothing was highlighted')); + } + const wanted = args.component ?? args.selector; + if (typeof wanted !== 'string' || !wanted.trim()) { + return { + markdown: refusal( + args.component !== undefined + ? 'Pass `component`: an instance id, class name or host tag.' + : 'Pass `selector`: a CSS selector.', + ), + }; + } + if (args.selector !== undefined) { + const target = page ? { pageId: page, selector: wanted } : wanted; + const shown = typeof target === 'string' ? { selector: target } : target; + void my.rpc.broadcast({ + method: 'highlight-in-page', + args: [{ ...shown, reveal: true, durationMs: AGENT_HIGHLIGHT_MS }], + optional: true, + }); + void my.rpc.broadcast({ + method: 'select-signal-component', + args: [target], + optional: true, + }); + return { + markdown: `Sent the CSS selector \`${wanted}\` to ${page ? `page \`${page}\`` : `every page (${pages.map((entry) => `\`${entry.pageId}\``).join(', ')})`}; the page outlines it only if an element matches, which the page does not confirm.`, + }; + } + const hits = componentHits(wanted, page); + const [hit] = hits; + if (!hit) { + const cut = truncationText(pages); + return { + markdown: refusal( + `No component instance matches \`${wanted}\`${page ? ` on page \`${page}\`` : ''}, so nothing was highlighted. Pass an instance id, class name or host tag from pangular:list-components, or a CSS selector as \`selector\`.${cut ? ` ${cut}` : ''}`, + ), + }; + } + const target = { pageId: hit.pageId, id: hit.node.id }; + void my.rpc.broadcast({ + method: 'highlight-in-page', + args: [{ ...target, reveal: true, durationMs: AGENT_HIGHLIGHT_MS }], + optional: true, + }); + void my.rpc.broadcast({ + method: 'select-signal-component', + args: [target], + optional: true, + }); + selectComponentOnPage(hit.pageId, hit.node.id); + const others = otherMatchesText(hits, wanted); + const otherPages = [ + ...new Set(hits.map((entry) => entry.pageId).filter((id) => id !== hit.pageId)), + ]; + const also = otherPages.length + ? ` It also matches on ${otherPages.map((id) => `\`${id}\``).join(', ')}. Pass \`page\` to pick another tab.` + : ''; + return { + markdown: `Sent a highlight request for \`${hit.node.name}\` (\`<${hit.node.tag}>\`, instance \`${hit.node.id}\` on page \`${hit.pageId}\`) and selected it.${also}${others ? `\n\n${others}` : ''}`, + }; + }, + }); + + agent.registerTool({ + id: 'pangular:inspect-component', + title: 'Inspect a component', + description: + 'Live details of a component instance (`component`: id, class or tag): inputs, outputs, properties, listeners, host directives, DI dependencies, change detection and source. Fields say `edit` or `locked`. `path` reads a nested value. Masked; cut at 20,000 characters.', + inputSchema: { + type: 'object', + properties: { + component: { + type: 'string', + description: 'Instance id (e.g. c12), class name (e.g. ProductCard) or host tag.', + }, + path: { + type: 'string', + description: + 'Read one value instead of the whole detail: an input or property name, then keys and indexes, such as `user.address` or `items[0]` (an index counts the entries of a Map or Set).', + }, + depth: { + type: 'number', + description: 'With `path`: levels to read below it, 1 to 5. Defaults to 1.', + }, + limit: { + type: 'number', + description: 'With `path`: most entries per object or list, 1 to 1000. Defaults to 30.', + }, + page: componentPageProperty, + }, + required: ['component'], + }, + handler: async (args: { + component: string; + path?: unknown; + depth?: unknown; + limit?: unknown; + page?: string; + }) => { + const page = pageArgument(args); + const valuePath = args.path === undefined || args.path === '' ? null : parsePath(args.path); + if (args.path !== undefined && args.path !== '' && !valuePath) { + return { + markdown: + 'Pass `path` as a property name, or a path such as `user.address` or `items[0]`.', + }; + } + const unknown = unknownComponentPage(page); + if (unknown) return unknown; + if (![...componentPages.values()].some((entry) => entry.roots.length)) { + return { markdown: noComponentTree('') }; + } + if (typeof args.component !== 'string' || !args.component.trim()) { + return { markdown: 'Pass `component`: an instance id, class name or host tag.' }; + } + const hits = componentHits(args.component, page); + const [hit] = hits; + if (!hit) { + return { + markdown: [ + `No component instance matches \`${args.component}\`. Pass an instance id, class name or host tag from the pangular:component-tree resource.`, + truncationText( + [...componentPages.values()].filter((entry) => !page || entry.pageId === page), + ), + ] + .filter(Boolean) + .join(' '), + }; + } + if (hit.node.kind === 'directive') { + return { markdown: untrusted(directiveHostText(hit.node, hit.pageId)) }; + } + const others = otherMatchesText(hits, args.component); + if (valuePath) { + const whole = (value: unknown) => + typeof value === 'number' && Number.isFinite(value) ? Math.round(value) : undefined; + const result = await componentValues.request(hit.pageId, { + id: hit.node.id, + path: valuePath, + depth: whole(args.depth), + limit: whole(args.limit), + }); + const text = valueResultText(result, { ...hit.node, pageId: hit.pageId }, valuePath); + return { markdown: others ? `${text}\n\n${others}` : text }; + } + selectComponentOnPage(hit.pageId, hit.node.id); + const detail = await waitForComponentDetail(hit.pageId, hit.node.id); + if (!detail) { + return { + markdown: `Selected \`${hit.node.name}\` (instance \`${hit.node.id}\` on page \`${hit.pageId}\`), but the page did not report its detail within 3 seconds. The instance may have been destroyed, or the tab may be in the background. Try again, or read the pangular:component-tree resource later.${others ? `\n\n${others}` : ''}`, + }; + } + return { + // The notice is part of the heading, so the cap counts it too. + markdown: inspectComponentText( + detail, + untrusted( + `Live detail of \`${detail.name}\` (\`<${detail.tag}>\`, instance \`${detail.id}\` on page \`${hit.pageId}\`), read from the running page.${others ? `\n\n${others}` : ''}`, + ), + ), + }; + }, + }); + + agent.registerTool({ + id: 'pangular:list-components', + title: 'List live components', + description: LIST_COMPONENTS_DESCRIPTION, + inputSchema: { + type: 'object', + properties: { + page: PAGE_ARGUMENT, + filter: { + type: 'string', + description: + 'Search: plain text found in a class name, host tag or host directive (case-insensitive), `` for a host tag and `[dir]` for a directive, as in ` } - + @if (!detail.nativeFetch) { + + } @@ -837,6 +839,12 @@ const HTTP_STATUS_OPTIONS: SelectOption[] = [ > {{ detail.url }}

+ @if (detail.nativeFetch) { +

+ Made with fetch() directly, which skips the HttpClient interceptor, so + a fault rule cannot mock it. +

+ } @if (rulePatternOf(detail); as pattern) {

Matched rule {{ pattern }} @@ -2483,6 +2491,8 @@ export class NetworkInspector { return page.calls.filter( (c) => c.side === 'client' && + // The page's own fetch() never goes through the transfer cache, so it is no cache miss. + !c.nativeFetch && !c.cacheHit && !c.mocked && c.at >= request.at && diff --git a/apps/docs/src/content/inspectors/ssr-http.md b/apps/docs/src/content/inspectors/ssr-http.md index 46ecff03..e5337c08 100644 --- a/apps/docs/src/content/inspectors/ssr-http.md +++ b/apps/docs/src/content/inspectors/ssr-http.md @@ -168,7 +168,7 @@ If nothing matches, **Clear filters** resets all three. While the filters hide t **Table** and **Waterfall** above the timeline switch its view. **Table** lists every call. **Waterfall** shows the page's client calls grouped by navigation, newest first. A navigation starts each time the page URL a call was made on changes. Each row has the method and path, a bar that starts at the call's offset in its navigation and spans its duration, and the status, offset and duration, such as `503 · +10 ms · 8 ms`. The status reads `ERR` for a network error, `cancelled` for a cancelled call, and adds `cache` for a transfer cache hit; failed calls and cache hits get a tinted label. The bar is the accent color for a network call, green for a transfer cache hit, red for a failed call and gray for a cancelled one. The count above the view reads how many client calls the waterfall shows out of all calls, such as **3 client calls of 7**. The rows are one `Tab` stop, and the arrow keys, `Home` and `End` move between them. The filters apply to both views, and the waterfall leaves out SSR calls. Click a row to open the same preview as in the table. -Click a row for a response preview. The preview opens under the timeline and takes focus. A JSON body is indented by 2 spaces. Other text, and a body clipped at 2000 characters, shows as recorded. The preview is redacted before it reaches the panel, so masked values stay masked. Under the body, **Request headers** and **Response headers** list the call's headers. Request headers are the ones the call had when it reached `withPangular()`, so headers that later interceptors add are not in the list. Response headers are the ones the app could read; a cross-origin response shows only the headers CORS exposes. `Authorization`, `Proxy-Authorization`, `Cookie` and `Set-Cookie` are always masked, whatever `redaction.unmask` says. Names that look secret or match `redaction.secretNames`, and JWTs and bearer tokens in any value, are masked too. The interceptor keeps only a reference to the headers Angular already built. The overlay reads and masks them when it sends the call, so a page without the devtools copies nothing. **Copy** puts the shown preview on the clipboard. **Copy as fetch** and **Copy as cURL** put the call on the clipboard as a `fetch()` statement or a `curl` command, with its method, URL and request headers. They are built from the masked copy the panel holds, so `[redacted]` stays where a secret was, and a comment at the top says to fill those values in. Request bodies are not recorded, so a `POST`, `PUT`, `PATCH` or `DELETE` gets a comment that says so. Relative client URLs get the panel's origin, as in **Export HAR**. The panel only copies text: it never sends the request. **Mock this request** fills in the [fault rule form](#fault-injection) from the call. **Close** or `Escape` returns focus to the row. A long timeline scrolls inside its own box. The timeline shows the page's client calls and the SSR calls made while rendering its first URL. **Clear timeline** empties it. +Click a row for a response preview. The preview opens under the timeline and takes focus. A JSON body is indented by 2 spaces. Other text, and a body clipped at 2000 characters, shows as recorded. The preview is redacted before it reaches the panel, so masked values stay masked. Under the body, **Request headers** and **Response headers** list the call's headers. Request headers are the ones the call had when it reached `withPangular()`, so headers that later interceptors add are not in the list. Response headers are the ones the app could read; a cross-origin response shows only the headers CORS exposes. `Authorization`, `Proxy-Authorization`, `Cookie` and `Set-Cookie` are always masked, whatever `redaction.unmask` says. Names that look secret or match `redaction.secretNames`, and JWTs and bearer tokens in any value, are masked too. The interceptor keeps only a reference to the headers Angular already built. The overlay reads and masks them when it sends the call, so a page without the devtools copies nothing. **Copy** puts the shown preview on the clipboard. **Copy as fetch** and **Copy as cURL** put the call on the clipboard as a `fetch()` statement or a `curl` command, with its method, URL and request headers. They are built from the masked copy the panel holds, so `[redacted]` stays where a secret was, and a comment at the top says to fill those values in. Request bodies are not recorded, so a `POST`, `PUT`, `PATCH` or `DELETE` gets a comment that says so. Relative client URLs get the panel's origin, as in **Export HAR**. The panel only copies text: it never sends the request. **Mock this request** fills in the [fault rule form](#fault-injection) from the call. A native `fetch()` call has no such button, since it skips the `HttpClient` interceptor that applies the rules. **Close** or `Escape` returns focus to the row. A long timeline scrolls inside its own box. The timeline shows the page's client calls and the SSR calls made while rendering its first URL. **Clear timeline** empties it. **Export HAR** saves the timeline as a HAR 1.2 file, named after the page path, such as `pangular-shop.har`. It holds the SSR request that served the page, with its kept response headers such as `Server-Timing`, then the server and client calls, oldest first, each with its method, URL, status, time, headers and response preview. Notes such as transfer cache, mocked, failed by a fault rule or cancelled go in each entry's `comment`. The file is written from the masked copy the panel holds, so secrets stay `[redacted]`. Request bodies are not recorded, and response bodies are the previews, clipped at 2000 characters. Relative client URLs get the panel's origin, which is the app's when the app's dev server serves the panel; in the Chrome extension they stay relative. Open the file in Chrome's Network panel, or attach it to a bug report. @@ -200,7 +200,7 @@ Each finding says what happened, how many milliseconds it costs where that can b The overlay also records the page's own `fetch()` calls, marked **fetch()** in the timeline and `native fetch` in `list-http-calls`. It wraps `window.fetch` once and only looks: the app gets the original response, and the body is never read, so these calls have no response preview. Their headers are captured and masked like those of `HttpClient` calls. `providePangularHttp()` starts recording at bootstrap; without it, recording starts when the overlay connects, so calls made before then are missing. -An `HttpClient` request on `withFetch()` that `withPangular()` records is listed once, as an `HttpClient` call: while it is in flight, a `fetch()` with the same method and URL is taken as Angular's own. A plain `fetch()` to that URL at the same moment is left out too. Calls to the devtools' own paths (`/__devframes/`) and Vite's module requests are never recorded. Fault rules and SSR overrides don't apply to native calls, and the server's own `fetch()` calls during a render are not recorded. +An `HttpClient` request on `withFetch()` that `withPangular()` records is listed once, as an `HttpClient` call: while it is in flight, a `fetch()` with the same method and URL is taken as Angular's own. A plain `fetch()` to that URL at the same moment is left out too. Calls to the devtools' own paths (`/__devframes/`) and Vite's module requests are never recorded. Fault rules and SSR overrides don't apply to native calls, and they are never listed under **Fetched again in the browser**, since they don't read the transfer cache. The server's own `fetch()` calls during a render are not recorded. ### Fault injection diff --git a/packages/devtools/src/__tests__/ssr-requests.test.ts b/packages/devtools/src/__tests__/ssr-requests.test.ts index dbe6520b..b180c981 100644 --- a/packages/devtools/src/__tests__/ssr-requests.test.ts +++ b/packages/devtools/src/__tests__/ssr-requests.test.ts @@ -349,6 +349,8 @@ describe('ssrRequestStory', () => { calls: [ httpCall({ id: 'c1', side: 'client', url: '/api/products', cacheHit: true, at: 1200 }), httpCall({ id: 'c2', side: 'client', url: '/api/quote', method: 'POST', at: 1200 }), + // The page's own fetch() to a URL the server fetched too: no transfer cache miss. + httpCall({ id: 'c3', side: 'client', url: '/api/products', at: 1300, nativeFetch: true }), ], }), ]; diff --git a/packages/devtools/src/rpc/ssr-tools.ts b/packages/devtools/src/rpc/ssr-tools.ts index 3fc90be6..2dbef6ec 100644 --- a/packages/devtools/src/rpc/ssr-tools.ts +++ b/packages/devtools/src/rpc/ssr-tools.ts @@ -185,6 +185,8 @@ export function ssrRequestStory( const refetched = (page?.calls ?? []).filter( (c) => c.side === 'client' && + // The page's own fetch() never reads the transfer cache, so it is no cache miss. + !c.nativeFetch && !c.cacheHit && !c.mocked && c.at >= request.at && From 01d97cc8fdd5be458cb95d500f50a4c320545cc5 Mon Sep 17 00:00:00 2001 From: erKam Date: Sun, 8 Nov 2026 07:17:20 +0000 Subject: [PATCH 26/32] ci: add MCP e2e cases for the server errors, web vitals and build tools The MCP e2e failed on list-server-errors, web-vitals, module-graph and chunk-sizes, which had no valid-arguments case in the harness. --- scripts/e2e-mcp.mjs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/e2e-mcp.mjs b/scripts/e2e-mcp.mjs index 2da9fddd..9232ebe8 100644 --- a/scripts/e2e-mcp.mjs +++ b/scripts/e2e-mcp.mjs @@ -646,6 +646,13 @@ const READ_PLAN = [ ['tanstack-inspect-query', null, () => ({ key: 'products' })], ['redux-inspect-stores', null, () => ({})], ['list-errors', null, () => ({})], + ['list-server-errors', null, () => ({})], + ['list-server-errors', null, () => ({ kind: 'ssr', limit: 5 })], + ['list-server-errors', null, () => ({ id: 's999' })], + ['web-vitals', null, () => ({})], + ['module-graph', null, () => ({ limit: 5 })], + ['module-graph', null, () => ({ search: 'main', limit: 5 })], + ['chunk-sizes', null, () => ({ limit: 5 })], ['explain-error', null, (ctx) => ({ id: ctx.errorId ?? 'e1' })], ['explain-error', null, () => ({ code: 'NG0100' })], ['list-console', null, () => ({ since: '30m', limit: 20 })], From 7a2813cab0207d5ed4c0e1da642f4709a98d8a91 Mon Sep 17 00:00:00 2001 From: erKam Date: Sun, 8 Nov 2026 07:18:07 +0000 Subject: [PATCH 27/32] fix(forms): read the default page in explain-field with only a path The other form tools fall back to the default page when a call names no form and no page, but explain-field passed its target as given, so a path-only call searched every tab's forms. Apply the same default page. --- packages/devtools/src/__tests__/forms-mcp.test.ts | 8 ++++++++ packages/devtools/src/devframe-forms-tools.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/devtools/src/__tests__/forms-mcp.test.ts b/packages/devtools/src/__tests__/forms-mcp.test.ts index 6954b9a6..cccf4bf6 100644 --- a/packages/devtools/src/__tests__/forms-mcp.test.ts +++ b/packages/devtools/src/__tests__/forms-mcp.test.ts @@ -302,6 +302,14 @@ describe('forms MCP tools', () => { expect(fallback).not.toContain('Signup'); }); + it('explain-field with only a path reads the default page', async () => { + const { call } = await withForms(); + // pg2 connected last and has only Profile, so `bio` is found without naming the form. + const field = await call('explain-field', { path: 'bio' }); + expect(field).toContain('`bio` in `Profile.form`'); + expect(field).not.toContain('Signup'); + }); + it('explain-invalid-forms and inspect-form-payload narrow to a path', async () => { const { call } = await withForms(); const explained = await call('explain-invalid-forms', { form: 'Signup', path: 'email' }); diff --git a/packages/devtools/src/devframe-forms-tools.ts b/packages/devtools/src/devframe-forms-tools.ts index 7a0b2467..ac15b7cd 100644 --- a/packages/devtools/src/devframe-forms-tools.ts +++ b/packages/devtools/src/devframe-forms-tools.ts @@ -217,7 +217,7 @@ export function registerFormsAgentTools(env: RegisterFormsAgentToolsEnv): { if (!state.forms.length) return { markdown: noForms }; const elsewhere = unknownFormPage(args); if (elsewhere) return elsewhere; - let target = { form: args?.form, path: args?.path, page: args?.page }; + let target = onDefaultFormPage({ form: args?.form, path: args?.path, page: args?.page }); if (args?.selector) { const located = await requestFormAction( { action: 'locate', selector: args.selector, page: args.page }, From 61ac246c0fc05df37324f1411ef4c4179bfaaba5 Mon Sep 17 00:00:00 2001 From: erKam Date: Sun, 8 Nov 2026 07:19:49 +0000 Subject: [PATCH 28/32] fix(router): refuse an unknown app in clear-router-navigations A call with an app id the page never reported fell through to the first app's branch and cleared the page's own history. Check the id against the page's apps and refuse it with the ids the page has. --- .../src/__tests__/router-clear.test.ts | 24 +++++++++++++++++++ packages/devtools/src/devframe.ts | 6 +++-- packages/devtools/src/rpc/router-tools.ts | 20 +++++++++++++++- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/packages/devtools/src/__tests__/router-clear.test.ts b/packages/devtools/src/__tests__/router-clear.test.ts index 5dc3baa5..3a216abd 100644 --- a/packages/devtools/src/__tests__/router-clear.test.ts +++ b/packages/devtools/src/__tests__/router-clear.test.ts @@ -128,6 +128,30 @@ describe('clear-router-navigations', () => { expect((await page()).apps!.find((a) => a.id === 'cart')!.navigations).toHaveLength(1); }); + it('refuses an app id the page does not report and clears nothing', async () => { + const { ctx, invoke, router } = await boot(); + const broadcast = vi.spyOn(ctx.rpc, 'broadcast'); + const apps = [ + { id: 'app-root', name: 'Shell', selector: 'app-root', primary: true }, + { id: 'cart', name: 'Cart', selector: 'cart-widget', navigations: [nav(7)] }, + ]; + await invoke('push-router', report('p1', [nav(1, { url: '/home' })], { apps })); + await expect( + invoke('clear-router-navigations', { pageId: 'p1', app: 'no-such-app' }), + ).rejects.toThrow('no app with the id "no-such-app"'); + await invoke('push-router', report('p2', [nav(2)])); + await expect(invoke('clear-router-navigations', { pageId: 'p2', app: 'cart' })).rejects.toThrow( + 'leave app out', + ); + const pages = (await router()).pages; + expect(pages.find((p) => p.pageId === 'p1')!.navigations).toHaveLength(1); + expect(pages.find((p) => p.pageId === 'p1')!.apps![1]!.navigations).toHaveLength(1); + expect(pages.find((p) => p.pageId === 'p2')!.navigations).toHaveLength(1); + expect(broadcast).not.toHaveBeenCalledWith( + expect.objectContaining({ method: 'pangular:router-clear' }), + ); + }); + it('keeps the list empty when the page reports after clearing', async () => { const { invoke, router } = await boot(); await invoke('push-router', report('p1', looping)); diff --git a/packages/devtools/src/devframe.ts b/packages/devtools/src/devframe.ts index ae60ad51..137eb376 100644 --- a/packages/devtools/src/devframe.ts +++ b/packages/devtools/src/devframe.ts @@ -131,6 +131,7 @@ import { } from './rpc/pipes-tools.ts'; import { clearRouterNavigations, + unknownRouterApp, currentRouter, expireRouterPages, isRouterReport, @@ -1247,8 +1248,9 @@ const pangular = defineDevframe({ const pageId = typeof target === 'string' ? target : record['pageId']; const app = typeof record['app'] === 'string' && record['app'] ? record['app'] : undefined; if (typeof pageId !== 'string') throw new Error('Give the id of the page to clear.'); - const page = routerPages.get(pageId); - const secondary = !!app && !!page?.apps?.some((a) => a.id === app && !a.primary); + const unknownApp = unknownRouterApp(routerPages, pageId, app); + if (unknownApp) throw new Error(unknownApp); + const secondary = routerPages.get(pageId)?.apps?.some((a) => a.id === app && !a.primary); if (clearRouterNavigations(routerPages, pageId, Date.now(), app)) { applyRouter(currentRouter(routerPages)); } diff --git a/packages/devtools/src/rpc/router-tools.ts b/packages/devtools/src/rpc/router-tools.ts index 5a501b24..235605e4 100644 --- a/packages/devtools/src/rpc/router-tools.ts +++ b/packages/devtools/src/rpc/router-tools.ts @@ -503,11 +503,28 @@ export function touchRouterPage(pages: Pages, pageId: unknown, now = Date.now()) return true; } +/** + * The refusal for an `app` the page did not report, so a typo never clears + * the first app instead; `undefined` without an app, or when the page is + * unknown or lists it. + */ +export function unknownRouterApp( + pages: Pages, + pageId: string, + appId: string | undefined, +): string | undefined { + const page = pages.get(pageId); + if (!appId || !page || page.apps?.some((app) => app.id === appId)) return undefined; + const known = (page.apps ?? []).map((app) => `\`${app.id}\``).join(', '); + return `Page ${pageId} reports no app with the id "${appId}". ${known ? `Its apps: ${known}.` : 'It runs one app; leave app out.'}`; +} + /** * Drops one page's navigation history, its loop warnings and its dropped * count. A navigation still in progress stays, as it does in the page. * With `appId`, only that app of a page that runs several is cleared; the - * first app's history is the page's own. Returns false when the page is unknown. + * first app's history is the page's own. Returns false when the page is + * unknown, or does not report `appId`. */ export function clearRouterNavigations( pages: Pages, @@ -517,6 +534,7 @@ export function clearRouterNavigations( ): boolean { const page = pages.get(pageId); if (!page) return false; + if (unknownRouterApp(pages, pageId, appId)) return false; const secondary = appId ? page.apps?.find((app) => app.id === appId && !app.primary) : undefined; if (secondary) { const next: RouterPage = { ...page, changedAt: now }; From 93285dde4d78bf50dcdae205ae355daf4748637b Mon Sep 17 00:00:00 2001 From: erKam Date: Sun, 8 Nov 2026 07:21:56 +0000 Subject: [PATCH 29/32] docs: list the new inspectors and fix the extra devframes and transfer cache notes Add performance and build to the PangularInspector type, replace the placeholder extra devframe example with a runnable one, describe the reserved id check as the hub now runs it, and say which transfer cache exclusions withPangular({transferCache}) can change. --- .../content/getting-started/configuration.md | 24 +++++++++++++++---- apps/docs/src/content/inspectors/ssr-http.md | 2 +- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/apps/docs/src/content/getting-started/configuration.md b/apps/docs/src/content/getting-started/configuration.md index 5f14124e..4d6df4e3 100644 --- a/apps/docs/src/content/getting-started/configuration.md +++ b/apps/docs/src/content/getting-started/configuration.md @@ -105,7 +105,9 @@ type PangularInspector = | 'analog' | 'capacitor' | 'errors' - | 'plugins'; + | 'plugins' + | 'performance' + | 'build'; type PangularAction = 'forms' | 'router' | 'ngrx' | 'http' | 'analog' | 'components' | 'tanstackQuery' | 'plugins'; @@ -297,14 +299,28 @@ It is off by default because it starts processes on your machine. A value other `extraDevframes` is an option of the Express hub and the Vite plugin only. It mounts other devframes on the hub that serves Pangular Inspector, so a team's own devtools or another library's devframe sit next to the Pangular docks without a second server. It takes the same input as `initHub({ devframes })` from `@devframes/hub`: devframe definitions, `{ devframe, dock }` entries to change a dock, promises and functions that return them. +This one adds a devframe with one RPC function. `defineDevframe` comes from the `devframe` package, which the hub runs on: + ```ts +import {defineDevframe} from 'devframe'; import {initPangularHub} from '@pangular-inspector/devtools/hub'; -import {acmeDevframe} from '@acme/devtools'; -const devtools = initPangularHub({extraDevframes: [acmeDevframe]}); +const teamTools = defineDevframe({ + id: 'team-tools', + name: 'Team tools', + version: '1.0.0', + packageName: 'team-tools', + homepage: 'https://example.com/team-tools', + description: "The team's own devtools", + setup(ctx) { + ctx.rpc.register({name: 'team-tools:ping', type: 'static', handler: () => 'pong'}); + }, +}); + +const devtools = initPangularHub({extraDevframes: [teamTools]}); ``` -The extra devframes run behind the same origin check, request gate and one-time code as Pangular Inspector. An entry with the id `pangular` would replace Pangular Inspector, so it is dropped with a warning, and a value that is not an array is ignored with a warning. +The extra devframes run behind the same origin check, request gate and one-time code as Pangular Inspector. The hub mounts Pangular Inspector under the id `pangular`, so an entry with that id is dropped with a warning. That includes a devframe a promise or a function resolves to, which is checked once it resolves; the other entries keep their order. A value that is not an array is ignored with a warning. ### LAN address diff --git a/apps/docs/src/content/inspectors/ssr-http.md b/apps/docs/src/content/inspectors/ssr-http.md index e5337c08..1a8b4e3b 100644 --- a/apps/docs/src/content/inspectors/ssr-http.md +++ b/apps/docs/src/content/inspectors/ssr-http.md @@ -147,7 +147,7 @@ The tab checks the TransferState after each server response, so **cached** is wh Every `HttpClient` request, tagged **SSR** or **Client**, and the page's own `fetch()` calls. Each row shows the method, the URL, the page that made it, the status, the time, and notes: - **fetch()**: the page called `fetch()` itself, not through `HttpClient`, for example a library or a plain `await fetch(...)`. See [Native fetch calls](#native-fetch-calls). -- **transfer cache**: the TransferState cache answered it. Only client requests get it, because the server never reads the cache; a server call shows its [transfer cache outcome](#transfer-cache-outcome) instead. A client request counts as a hit only when its method, response type, URL, body and params match the entry Angular stored, and Angular would cache it: no `transferCache: false`, auth headers, credentials or no-cache request. +- **transfer cache**: the TransferState cache answered it. Only client requests get it, because the server never reads the cache; a server call shows its [transfer cache outcome](#transfer-cache-outcome) instead. A client request counts as a hit only when its method, response type, URL, body and params match the entry Angular stored, and Angular would cache it: no `transferCache: false`, and by default no auth headers, credentials or no-cache request. `withPangular({transferCache})` changes those defaults with `includeRequestsWithAuthHeaders`, `includeRequestsWithCredentials` and `includeNonCacheableRequests`; see [Transfer cache options](#transfer-cache-options). - **delayed N ms**: a fault rule held it back. - **mocked**: a fault rule answered it with a status below 400. - **faulted**: a fault rule failed it with a status of 400 or more, or with a network error (status 0, shown as **ERR**). From e44cec94b0eeed1a375376d9af42c0f9cd086175 Mon Sep 17 00:00:00 2001 From: erKam Date: Sun, 8 Nov 2026 07:30:53 +0000 Subject: [PATCH 30/32] ci: record the line counts #490 and #491 added in the lint baseline network-inspector.ts, store-inspector.ts, config.ts and devframe.ts grew with the main merge; the refetch tools went to their own module, so devframe.ts keeps only the call. --- lint-baseline.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lint-baseline.json b/lint-baseline.json index c0211582..42458de3 100644 --- a/lint-baseline.json +++ b/lint-baseline.json @@ -24,7 +24,7 @@ "pangular(max-lines)": 2742 }, "app/src/pages/network-inspector.ts": { - "pangular(max-lines)": 3033 + "pangular(max-lines)": 3143 }, "app/src/pages/pipes-inspector.ts": { "pangular(max-lines)": 1612 @@ -33,7 +33,7 @@ "pangular(max-lines)": 2547 }, "app/src/pages/store-inspector.ts": { - "pangular(max-lines)": 2836 + "pangular(max-lines)": 2870 }, "app/src/pages/store-types.ts": { "typescript(no-explicit-any)": 1 @@ -232,13 +232,13 @@ "unicorn(prefer-string-starts-ends-with)": 1 }, "packages/devtools/src/config.ts": { - "pangular(max-lines)": 2767 + "pangular(max-lines)": 2778 }, "packages/devtools/src/console-tap.ts": { "unicorn(no-useless-spread)": 1 }, "packages/devtools/src/devframe.ts": { - "pangular(max-lines)": 3224, + "pangular(max-lines)": 3238, "unicorn(no-useless-spread)": 1 }, "packages/devtools/src/dom-diff.ts": { From 62c80da529db4ff2e625a2cbfb8c060c56a87696 Mon Sep 17 00:00:00 2001 From: erKam Date: Sun, 8 Nov 2026 07:35:09 +0000 Subject: [PATCH 31/32] ci: clear the setter warnings main brought and record its panel line counts Three panel tests returned a value from a scrollTop setter; give the setters a block body. app.ts and component-tree.ts grew with #490, so record their line counts, and lower the count http-rule-tools.test.ts no longer reaches. --- app/src/__tests__/app-shell.test.ts | 8 ++++++-- app/src/__tests__/network-live-sort.test.ts | 8 ++++++-- app/src/__tests__/view-memory.test.ts | 4 +++- lint-baseline.json | 7 ++----- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/__tests__/app-shell.test.ts b/app/src/__tests__/app-shell.test.ts index 4525f83c..5d6592ab 100644 --- a/app/src/__tests__/app-shell.test.ts +++ b/app/src/__tests__/app-shell.test.ts @@ -334,7 +334,9 @@ describe('App scroll memory', () => { Object.defineProperty(main, 'scrollTop', { configurable: true, get: () => top, - set: (value: number) => (top = value), + set: (value: number) => { + top = value; + }, }); const buttons = () => [...host(fixture).querySelectorAll('nav button')]; // An earlier test can leave another tab in the URL. @@ -373,7 +375,9 @@ describe('App scroll memory while content loads', () => { Object.defineProperty(main, 'scrollTop', { configurable: true, get: () => top, - set: (value: number) => (top = Math.max(0, Math.min(value, max))), + set: (value: number) => { + top = Math.max(0, Math.min(value, max)); + }, }); const buttons = () => [...host(fixture).querySelectorAll('nav button')]; buttons()[0].click(); diff --git a/app/src/__tests__/network-live-sort.test.ts b/app/src/__tests__/network-live-sort.test.ts index 19c7b77b..0ed2988e 100644 --- a/app/src/__tests__/network-live-sort.test.ts +++ b/app/src/__tests__/network-live-sort.test.ts @@ -157,7 +157,9 @@ describe('the network live controls', () => { Object.defineProperty(wrap, 'scrollTop', { configurable: true, get: () => top, - set: (value: number) => (top = value), + set: (value: number) => { + top = value; + }, }); const follow = host().querySelector('app-live-controls input')!; expect(follow.checked).toBe(false); @@ -204,7 +206,9 @@ describe('Follow newest with SSR requests on the page', () => { Object.defineProperty(wrap, 'scrollTop', { configurable: true, get: () => tops[i], - set: (value: number) => (tops[i] = value), + set: (value: number) => { + tops[i] = value; + }, }), ); host().querySelector('app-live-controls input')!.click(); diff --git a/app/src/__tests__/view-memory.test.ts b/app/src/__tests__/view-memory.test.ts index 210b4357..1c34d707 100644 --- a/app/src/__tests__/view-memory.test.ts +++ b/app/src/__tests__/view-memory.test.ts @@ -72,7 +72,9 @@ describe('restoreScroll', () => { Object.defineProperty(el, 'scrollTop', { configurable: true, get: () => top, - set: (value: number) => (top = Math.max(0, Math.min(value, max()))), + set: (value: number) => { + top = Math.max(0, Math.min(value, max())); + }, }); return el; } diff --git a/lint-baseline.json b/lint-baseline.json index 42458de3..96857841 100644 --- a/lint-baseline.json +++ b/lint-baseline.json @@ -12,13 +12,13 @@ "eslint(no-setter-return)": 1 }, "app/src/app.ts": { - "pangular(max-lines)": 1970 + "pangular(max-lines)": 1999 }, "app/src/pages/analog-inspector.ts": { "pangular(max-lines)": 2743 }, "app/src/pages/component-tree.ts": { - "pangular(max-lines)": 2974 + "pangular(max-lines)": 2982 }, "app/src/pages/di-inspector.ts": { "pangular(max-lines)": 2742 @@ -132,9 +132,6 @@ "packages/devtools/src/__tests__/forms.test.ts": { "typescript(no-explicit-any)": 6 }, - "packages/devtools/src/__tests__/http-rule-tools.test.ts": { - "eslint(no-unused-vars)": 1 - }, "packages/devtools/src/__tests__/http.test.ts": { "eslint(no-unused-vars)": 1, "typescript(no-explicit-any)": 1 From 9ee60ff6d1dfd70e1361725b3136e5149752ec85 Mon Sep 17 00:00:00 2001 From: erKam Date: Sun, 8 Nov 2026 07:43:18 +0000 Subject: [PATCH 32/32] build(extension): rebuild extension/ui after the main merge The merge kept main's index.html while the conflicting asset files were dropped, so the committed bundle loaded a script that no longer exists and the extension panel never connected. --- .../browser-agent-rpc-KH4HHvHf-DqNFcBHr.js | 1 + extension/ui/assets/index-Dw3jx7J8.js | 28113 ++++++++++++++++ extension/ui/index.html | 2 +- 3 files changed, 28115 insertions(+), 1 deletion(-) create mode 100644 extension/ui/assets/browser-agent-rpc-KH4HHvHf-DqNFcBHr.js create mode 100644 extension/ui/assets/index-Dw3jx7J8.js diff --git a/extension/ui/assets/browser-agent-rpc-KH4HHvHf-DqNFcBHr.js b/extension/ui/assets/browser-agent-rpc-KH4HHvHf-DqNFcBHr.js new file mode 100644 index 00000000..718def3d --- /dev/null +++ b/extension/ui/assets/browser-agent-rpc-KH4HHvHf-DqNFcBHr.js @@ -0,0 +1 @@ +import{t as e}from"./index-Dw3jx7J8.js";var t=Symbol.for(`devframe:browser-agent-registry`),{tools:n,listeners:r}=globalThis[t]??={tools:new Map,listeners:new Set};function i(){return[...n.values()]}function a(e){return r.add(e),()=>r.delete(e)}var o=`devframe:client-id`,s;function c(t=globalThis.window){try{let n=t?.sessionStorage;if(n){let t=n.getItem(o);return t||(t=e(),n.setItem(o,t)),t}}catch{}return s??=e(),s}function l(){let e=typeof document>`u`?void 0:document;return{url:e?.location?.href??``,title:e?.title??``,visible:!e||e.visibilityState===`visible`,focused:e?.hasFocus()??!0}}function u(e){e.client.register({name:`devframe:agent:invoke-client-tool`,type:`action`,jsonSerializable:!0,handler:async(e,t)=>{let n=i().find(t=>t.id===e);if(!n)throw Error(`[devframe/agent] browser tool "${e}" not found`);return await n.invoke(t)}});let t=!1,n=!1,r=0,o=()=>{t||n||(t=!0,queueMicrotask(async()=>{if(t=!1,n)return;let a=i().map(({invoke:e,...t})=>t);(a.length!==0||r!==0)&&(r=a.length,await e.callOptional(`devframe:agent:sync-client-tools`,c(),a,l()).catch(()=>{}))}))},s=a(o),u=e.events.on(`connection:status`,e=>{e===`connected`&&o()}),d=typeof window>`u`?void 0:window;return d?.addEventListener(`focus`,o),d?.addEventListener(`blur`,o),d?.document.addEventListener(`visibilitychange`,o),o(),()=>{n=!0,s(),u(),d?.removeEventListener(`focus`,o),d?.removeEventListener(`blur`,o),d?.document.removeEventListener(`visibilitychange`,o)}}export{u as setupBrowserAgentRpcBridge}; \ No newline at end of file diff --git a/extension/ui/assets/index-Dw3jx7J8.js b/extension/ui/assets/index-Dw3jx7J8.js new file mode 100644 index 00000000..adc401a8 --- /dev/null +++ b/extension/ui/assets/index-Dw3jx7J8.js @@ -0,0 +1,28113 @@ +(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=Object.defineProperty,t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,i=(t,n,r)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[n]=r,a=(e,a)=>{for(var o in a||={})n.call(a,o)&&i(e,o,a[o]);if(t)for(var o of t(a))r.call(a,o)&&i(e,o,a[o]);return e},o=(e,t,n)=>(i(e,typeof t==`symbol`?t:t+``,n),n),s=globalThis;function c(e){let t=s.__Zone_symbol_prefix;return(typeof t==`string`?t:`__zone_symbol__`)+e}function l(){let e=s.performance;function t(t){e&&e.mark&&e.mark(t)}function n(t,n){e&&e.measure&&e.measure(t,n)}t(`Zone`);let r=class e{constructor(e,t){o(this,`_parent`),o(this,`_name`),o(this,`_properties`),o(this,`_zoneDelegate`),this._parent=e,this._name=t?t.name||`unnamed`:``,this._properties=t&&t.properties||{},this._zoneDelegate=new l(this,this._parent&&this._parent._zoneDelegate,t)}static assertZonePatched(){if(s.Promise!==me.ZoneAwarePromise)throw Error("Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten.\nMost likely cause is that a Promise polyfill has been loaded after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.)")}static get root(){let t=e.current;for(;t.parent;)t=t.parent;return t}static get current(){return ge.zone}static get currentTask(){return _e}static __load_patch(r,i,a=!1){if(Object.hasOwn(me,r)){let e=s[c(`forceDuplicateZoneCheck`)]===!0;if(!a&&e)throw Error(`Already loaded patch: `+r)}else if(!s[`__Zone_disable_`+r]){let a=`Zone:`+r;t(a),me[r]=i(s,e,he),n(a,a)}}get parent(){return this._parent}get name(){return this._name}get(e){let t=this.getZoneWith(e);if(t)return t._properties[e]}getZoneWith(e){let t=this;for(;t;){if(Object.hasOwn(t._properties,e))return t;t=t._parent}return null}fork(e){if(!e)throw Error(`ZoneSpec required!`);return this._zoneDelegate.fork(this,e)}wrap(e,t){if(typeof e!=`function`)throw Error(`Expecting function got: `+e);let n=this._zoneDelegate.intercept(this,e,t),r=this;return function(){return r.runGuarded(n,this,arguments,t)}}run(e,t,n,r){ge={parent:ge,zone:this};try{return this._zoneDelegate.invoke(this,e,t,n,r)}finally{ge=ge.parent}}runGuarded(e,t=null,n,r){ge={parent:ge,zone:this};try{try{return this._zoneDelegate.invoke(this,e,t,n,r)}catch(e){if(this._zoneDelegate.handleError(this,e))throw e}}finally{ge=ge.parent}}runTask(e,t,n){if(e.zone!=this)throw Error(`A task can only be run in the zone of creation! (Creation: `+(e.zone||ie).name+`; Execution: `+this.name+`)`);let r=e,{type:i,data:{isPeriodic:a=!1,isRefreshable:o=!1}={}}=e;if(e.state===ae&&(i===pe||i===fe))return;let s=e.state!=ce;s&&r._transitionTo(ce,se);let c=_e;_e=r,ge={parent:ge,zone:this};try{i==fe&&e.data&&!a&&!o&&(e.cancelFn=void 0);try{return this._zoneDelegate.invokeTask(this,r,t,n)}catch(e){if(this._zoneDelegate.handleError(this,e))throw e}}finally{let t=e.state;if(t!==ae&&t!==ue){if(i==pe||a||o&&t===oe)s&&r._transitionTo(se,ce,oe);else{let e=r._zoneDelegates;this._updateTaskCount(r,-1),s&&r._transitionTo(ae,ce,ae),o&&(r._zoneDelegates=e)}}ge=ge.parent,_e=c}}scheduleTask(e){if(e.zone&&e.zone!==this){let t=this;for(;t;){if(t===e.zone)throw Error(`can not reschedule task to ${this.name} which is descendants of the original zone ${e.zone.name}`);t=t.parent}}e._transitionTo(oe,ae);let t=[];e._zoneDelegates=t,e._zone=this;try{e=this._zoneDelegate.scheduleTask(this,e)}catch(t){throw e._transitionTo(ue,oe,ae),this._zoneDelegate.handleError(this,t),t}return e._zoneDelegates===t&&this._updateTaskCount(e,1),e.state==oe&&e._transitionTo(se,oe),e}scheduleMicroTask(e,t,n,r){return this.scheduleTask(new u(de,e,t,n,r,void 0))}scheduleMacroTask(e,t,n,r,i){return this.scheduleTask(new u(fe,e,t,n,r,i))}scheduleEventTask(e,t,n,r,i){return this.scheduleTask(new u(pe,e,t,n,r,i))}cancelTask(e){if(e.zone!=this)throw Error(`A task can only be cancelled in the zone of creation! (Creation: `+(e.zone||ie).name+`; Execution: `+this.name+`)`);if(e.state===se||e.state===ce){e._transitionTo(le,se,ce);try{this._zoneDelegate.cancelTask(this,e)}catch(t){throw e._transitionTo(ue,le),this._zoneDelegate.handleError(this,t),t}return this._updateTaskCount(e,-1),e._transitionTo(ae,le),e.runCount=-1,e}}_updateTaskCount(e,t){let n=e._zoneDelegates;t==-1&&(e._zoneDelegates=null);for(let r=0;re.hasTask(n,r),onScheduleTask:(e,t,n,r)=>e.scheduleTask(n,r),onInvokeTask:(e,t,n,r,i,a)=>e.invokeTask(n,r,i,a),onCancelTask:(e,t,n,r)=>e.cancelTask(n,r)};class l{constructor(e,t,n){o(this,`_zone`),o(this,`_taskCounts`,{microTask:0,macroTask:0,eventTask:0}),o(this,`_forkDlgt`),o(this,`_forkZS`),o(this,`_forkCurrZone`),o(this,`_interceptDlgt`),o(this,`_interceptZS`),o(this,`_interceptCurrZone`),o(this,`_invokeDlgt`),o(this,`_invokeZS`),o(this,`_invokeCurrZone`),o(this,`_handleErrorDlgt`),o(this,`_handleErrorZS`),o(this,`_handleErrorCurrZone`),o(this,`_scheduleTaskDlgt`),o(this,`_scheduleTaskZS`),o(this,`_scheduleTaskCurrZone`),o(this,`_invokeTaskDlgt`),o(this,`_invokeTaskZS`),o(this,`_invokeTaskCurrZone`),o(this,`_cancelTaskDlgt`),o(this,`_cancelTaskZS`),o(this,`_cancelTaskCurrZone`),o(this,`_hasTaskDlgt`),o(this,`_hasTaskDlgtOwner`),o(this,`_hasTaskZS`),o(this,`_hasTaskCurrZone`),this._zone=e,this._forkZS=n&&(n&&n.onFork?n:t._forkZS),this._forkDlgt=n&&(n.onFork?t:t._forkDlgt),this._forkCurrZone=n&&(n.onFork?this._zone:t._forkCurrZone),this._interceptZS=n&&(n.onIntercept?n:t._interceptZS),this._interceptDlgt=n&&(n.onIntercept?t:t._interceptDlgt),this._interceptCurrZone=n&&(n.onIntercept?this._zone:t._interceptCurrZone),this._invokeZS=n&&(n.onInvoke?n:t._invokeZS),this._invokeDlgt=n&&(n.onInvoke?t:t._invokeDlgt),this._invokeCurrZone=n&&(n.onInvoke?this._zone:t._invokeCurrZone),this._handleErrorZS=n&&(n.onHandleError?n:t._handleErrorZS),this._handleErrorDlgt=n&&(n.onHandleError?t:t._handleErrorDlgt),this._handleErrorCurrZone=n&&(n.onHandleError?this._zone:t._handleErrorCurrZone),this._scheduleTaskZS=n&&(n.onScheduleTask?n:t._scheduleTaskZS),this._scheduleTaskDlgt=n&&(n.onScheduleTask?t:t._scheduleTaskDlgt),this._scheduleTaskCurrZone=n&&(n.onScheduleTask?this._zone:t._scheduleTaskCurrZone),this._invokeTaskZS=n&&(n.onInvokeTask?n:t._invokeTaskZS),this._invokeTaskDlgt=n&&(n.onInvokeTask?t:t._invokeTaskDlgt),this._invokeTaskCurrZone=n&&(n.onInvokeTask?this._zone:t._invokeTaskCurrZone),this._cancelTaskZS=n&&(n.onCancelTask?n:t._cancelTaskZS),this._cancelTaskDlgt=n&&(n.onCancelTask?t:t._cancelTaskDlgt),this._cancelTaskCurrZone=n&&(n.onCancelTask?this._zone:t._cancelTaskCurrZone),this._hasTaskZS=null,this._hasTaskDlgt=null,this._hasTaskDlgtOwner=null,this._hasTaskCurrZone=null;let r=n&&n.onHasTask,i=t&&t._hasTaskZS;(r||i)&&(this._hasTaskZS=r?n:a,this._hasTaskDlgt=t,this._hasTaskDlgtOwner=this,this._hasTaskCurrZone=this._zone,n.onScheduleTask||(this._scheduleTaskZS=a,this._scheduleTaskDlgt=t,this._scheduleTaskCurrZone=this._zone),n.onInvokeTask||(this._invokeTaskZS=a,this._invokeTaskDlgt=t,this._invokeTaskCurrZone=this._zone),n.onCancelTask||(this._cancelTaskZS=a,this._cancelTaskDlgt=t,this._cancelTaskCurrZone=this._zone))}get zone(){return this._zone}fork(e,t){return this._forkZS?this._forkZS.onFork(this._forkDlgt,this.zone,e,t):new i(e,t)}intercept(e,t,n){return this._interceptZS?this._interceptZS.onIntercept(this._interceptDlgt,this._interceptCurrZone,e,t,n):t}invoke(e,t,n,r,i){return this._invokeZS?this._invokeZS.onInvoke(this._invokeDlgt,this._invokeCurrZone,e,t,n,r,i):t.apply(n,r)}handleError(e,t){return!this._handleErrorZS||this._handleErrorZS.onHandleError(this._handleErrorDlgt,this._handleErrorCurrZone,e,t)}scheduleTask(e,t){let n=t;if(this._scheduleTaskZS)this._hasTaskZS&&n._zoneDelegates.push(this._hasTaskDlgtOwner),n=this._scheduleTaskZS.onScheduleTask(this._scheduleTaskDlgt,this._scheduleTaskCurrZone,e,t),n||=t;else if(t.scheduleFn)t.scheduleFn(t);else if(t.type==de)ne(t);else throw Error(`Task is missing scheduleFn.`);return n}invokeTask(e,t,n,r){return this._invokeTaskZS?this._invokeTaskZS.onInvokeTask(this._invokeTaskDlgt,this._invokeTaskCurrZone,e,t,n,r):t.callback.apply(n,r)}cancelTask(e,t){let n;if(this._cancelTaskZS)n=this._cancelTaskZS.onCancelTask(this._cancelTaskDlgt,this._cancelTaskCurrZone,e,t);else{if(!t.cancelFn)throw Error(`Task is not cancelable`);n=t.cancelFn(t)}return n}hasTask(e,t){try{this._hasTaskZS&&this._hasTaskZS.onHasTask(this._hasTaskDlgt,this._hasTaskCurrZone,e,t)}catch(t){this.handleError(e,t)}}_updateTaskCount(e,t){let n=this._taskCounts,r=n[e],i=n[e]=r+t;if(i<0)throw Error(`More tasks executed then were scheduled.`);if(r==0||i==0){let t={microTask:n.microTask>0,macroTask:n.macroTask>0,eventTask:n.eventTask>0,change:e};this.hasTask(this._zone,t)}}}class u{constructor(e,t,n,r,i,a){if(o(this,`type`),o(this,`source`),o(this,`invoke`),o(this,`callback`),o(this,`data`),o(this,`scheduleFn`),o(this,`cancelFn`),o(this,`_zone`,null),o(this,`runCount`,0),o(this,`_zoneDelegates`,null),o(this,`_state`,`notScheduled`),this.type=e,this.source=t,this.data=r,this.scheduleFn=i,this.cancelFn=a,!n)throw Error(`callback is not defined`);this.callback=n;let c=this;this.invoke=e===pe&&r&&r.useG?u.invokeTask:function(){return u.invokeTask.call(s,c,this,arguments)}}static invokeTask(e,t,n){e||=this,ve++;try{return e.runCount++,e.zone.runTask(e,t,n)}finally{try{ve===1&&!s[m]&&re()}finally{ve--}}}get zone(){return this._zone}get state(){return this._state}cancelScheduleRequest(){this._transitionTo(ae,oe)}_transitionTo(e,t,n){if(this._state===t||this._state===n)this._state=e,e==ae&&(this._zoneDelegates=null);else throw Error(`${this.type} '${this.source}': can not transition to '${e}', expecting state '${t}'${n?` or '`+n+`'`:``}, was '${this._state}'.`)}toString(){return this.data&&this.data.handleId!==void 0?this.data.handleId.toString():Object.prototype.toString.call(this)}toJSON(){return{type:this.type,state:this.state,source:this.source,zone:this.zone.name,runCount:this.runCount}}}let d=c(`setTimeout`),f=c(`Promise`),p=c(`then`),m=c(`enable_native_microtask_draining`),ee=[],h=!1,g;function te(e){!g&&s[f]&&(g=s[f].resolve(0)),g?(g[p]??g.then).call(g,e):s[d](e,0)}function ne(e){let t=s[m],n=t&&ee.length===0&&!h,r=!t&&ve===0&&ee.length===0;(n||r)&&te(re),e&&ee.push(e)}function re(){if(!h){h=!0;try{for(;ee.length;){let e=ee;ee=[];for(let t of e)try{t.zone.runTask(t,null,null)}catch(e){he.onUnhandledError(e)}}}finally{if(s[m])h=!1,he.microtaskDrainDone();else try{he.microtaskDrainDone()}finally{h=!1}}}}let ie={name:`NO ZONE`},ae=`notScheduled`,oe=`scheduling`,se=`scheduled`,ce=`running`,le=`canceling`,ue=`unknown`,de=`microTask`,fe=`macroTask`,pe=`eventTask`,me=Object.create(null),he={symbol:c,currentZoneFrame:()=>ge,onUnhandledError:ye,microtaskDrainDone:ye,scheduleMicroTask:ne,showUncaughtError:()=>!i[c(`ignoreConsoleErrorUncaughtError`)],patchEventTarget:()=>[],patchOnProperties:ye,patchMethod:()=>ye,bindArguments:()=>[],patchThen:()=>ye,patchMacroTask:()=>ye,patchEventPrototype:()=>ye,getGlobalObjects:()=>void 0,ObjectDefineProperty:()=>ye,ObjectGetOwnPropertyDescriptor:()=>void 0,ObjectCreate:()=>void 0,ArraySlice:()=>[],patchClass:()=>ye,wrapWithCurrentZone:()=>ye,filterProperties:()=>[],attachOriginToPatched:()=>ye,_redefineProperty:()=>ye,patchCallbacks:()=>ye,nativeScheduleMicroTask:te},ge={parent:null,zone:new i(null,null)},_e=null,ve=0;function ye(){}return n(`Zone`,`Zone`),i}function u(){let e=globalThis,t=e[c(`forceDuplicateZoneCheck`)]===!0;if(e.Zone&&(t||typeof e.Zone.__symbol__!=`function`))throw Error(`Zone already loaded.`);return e.Zone??=l(),e.Zone}var d=Object.getOwnPropertyDescriptor,f=Object.defineProperty,p=Object.getPrototypeOf,m=Object.create,ee=Array.prototype.slice,h=`addEventListener`,g=`removeEventListener`,te=c(h),ne=c(g),re=`true`,ie=`false`,ae=c(``);function oe(e,t){return Zone.current.wrap(e,t)}function se(e,t,n,r,i){return Zone.current.scheduleMacroTask(e,t,n,r,i)}var ce=c,le=typeof window<`u`,ue=le?window:void 0,de=le&&ue||globalThis,fe=`removeAttribute`;function pe(e,t){for(let n=e.length-1;n>=0;n--)typeof e[n]==`function`&&(e[n]=oe(e[n],t+`_`+n));return e}function me(e,t){let n=e.constructor.name;for(let r=0;r{let t=function(){return e.apply(this,pe(arguments,n+`.`+i))};return ke(t,e),t})(a)}}}function he(e){return e?e.writable===!1?!1:typeof e.get!=`function`||e.set!==void 0:!0}var ge=typeof WorkerGlobalScope<`u`&&self instanceof WorkerGlobalScope,_e=!(`nw`in de)&&de.process!==void 0&&de.process.toString()===`[object process]`,ve=!_e&&!ge&&!!(le&&ue.HTMLElement),ye=de.process!==void 0&&de.process.toString()===`[object process]`&&!ge&&!!(le&&ue.HTMLElement),be=Object.create(null),xe=ce(`enable_beforeunload`),Se=function(e){if(e||=de.event,!e)return;let t=be[e.type];t||=be[e.type]=ce(`ON_PROPERTY`+e.type);let n=this||e.target||de,r=n[t],i;if(ve&&n===ue&&e.type===`error`){let t=e;i=r&&r.call(this,t.message,t.filename,t.lineno,t.colno,t.error),i===!0&&e.preventDefault()}else i=r&&r.apply(this,arguments),e.type===`beforeunload`&&de[xe]&&typeof i==`string`?e.returnValue=i:i!=null&&!i&&e.preventDefault();return i};function Ce(e,t,n){let r=d(e,t);if(!r&&n&&d(n,t)&&(r={enumerable:!0,configurable:!0}),!r||!r.configurable)return;let i=ce(`on`+t+`patched`);if(Object.hasOwn(e,i)&&e[i])return;delete r.writable,delete r.value;let a=r.get,o=r.set,s=t.slice(2),c=be[s];c||=be[s]=ce(`ON_PROPERTY`+s),r.set=function(t){let n=this;!n&&e===de&&(n=de),n&&(typeof n[c]==`function`&&n.removeEventListener(s,Se),o?.call(n,null),n[c]=t,typeof t==`function`&&n.addEventListener(s,Se,!1))},r.get=function(){let n=this;if(!n&&e===de&&(n=de),!n)return null;let i=n[c];if(i)return i;if(a){let e=a.call(this);if(e)return r.set.call(this,e),typeof n[fe]==`function`&&n.removeAttribute(t),e}return null},f(e,t,r),e[i]=!0}function we(e,t,n){if(t)for(let r=0;rfunction(t,r){let a=n(t,r);return a.cbIdx>=0&&typeof r[a.cbIdx]==`function`?se(a.name,r[a.cbIdx],a,i):e.apply(t,r)})}function ke(e,t){e[ce(`OriginalDelegate`)]=t}function Ae(e){return typeof e==`function`}function je(e){return typeof e==`number`}var Me={useG:!0},Ne=Object.create(null),Pe={},Fe=RegExp(`^`+ae+`(\\w+)(true|false)$`),Ie=ce(`propagationStopped`),Le=[`capture`,`once`,`passive`,`signal`];function Re(e,t){let n=(t?t(e):e)+ie,r=(t?t(e):e)+re,i=ae+n,a=ae+r;Ne[e]={[ie]:i,[re]:a}}function ze(e,t,n,r){let i=r&&r.add||h,o=r&&r.rm||g,s=r&&r.listeners||`eventListeners`,c=r&&r.rmAll||`removeAllListeners`,l=ce(i),u=`.`+i+`:`,d=function(e,t,n){if(e.isRemoved)return;let r=e.callback;typeof r==`object`&&r.handleEvent&&(e.callback=e=>r.handleEvent(e),e.originalDelegate=r);let i;try{e.invoke(e,t,[n])}catch(e){i=e}let a=e.options;if(a&&typeof a==`object`&&a.once){let r=e.originalDelegate?e.originalDelegate:e.callback;t[o].call(t,n.type,r,a)}return i};function f(n,r,i){if(r||=e.event,!r)return;let a=n||r.target||e,o=a[Ne[r.type][i?re:ie]];if(o){let e=[];if(o.length===1){let t=d(o[0],a,r);t&&e.push(t)}else{let t=o.slice();for(let n=0;n{throw r})}}}let m=function(e){return f(this,e,!1)},ee=function(e){return f(this,e,!0)};function te(t,n){if(!t)return!1;let r=!0;n&&n.useG!==void 0&&(r=n.useG);let d=n&&n.vh,f=!0;n&&n.chkDup!==void 0&&(f=n.chkDup);let h=!1;n&&n.rt!==void 0&&(h=n.rt);let g=t;for(;g&&!Object.hasOwn(g,i);)g=p(g);if(!g&&t[i]&&(g=t),!g||g[l])return!1;let te=n&&n.eventNameToString,ne={},oe=g[l]=g[i],se=g[ce(o)]=g[o],le=g[ce(s)]=g[s],ue=g[ce(c)]=g[c],de;n&&n.prepend&&(de=g[ce(n.prepend)]=g[n.prepend]);function fe(e,t){return t?typeof e==`boolean`?{capture:e,passive:!0}:e?(typeof e==`object`&&e.passive!==!1&&(e.passive=!0),e):{passive:!0}:e}let pe=function(e){if(!ne.isExisting)return oe.call(ne.target,ne.eventName,ne.capture?ee:m,ne.options)},me=function(e){if(!e.isRemoved){let t=Ne[e.eventName],n;t&&(n=t[e.capture?re:ie]);let r=n&&e.target[n];if(r){for(let t=0;tve.zone.cancelTask(ve);t.call(g,`abort`,e,{once:!0}),ve.removeAbortListener=()=>g.removeEventListener(`abort`,e)}if(ne.target=null,ge&&(ge.taskData=null),oe&&(ne.options.once=!0),typeof ve.options!=`boolean`&&(ve.options=h),ve.target=l,ve.capture=ae,ve.eventName=u,m&&(ve.originalDelegate=p),c?ue.unshift(ve):ue.push(ve),s)return l}};return g[i]=Te(oe,u,ye,be,h),de&&(g.prependListener=Te(de,`.prependListener:`,ge,be,h,!0)),g[o]=function(){let t=this||e,r=arguments[0];n&&n.transferEventName&&(r=n.transferEventName(r));let i=arguments[2],a=i?typeof i==`boolean`||i.capture:!1,o=arguments[1];if(!o)return se.apply(this,arguments);if(d&&!d(se,o,t,arguments))return;let s=Ne[r],c;s&&(c=s[a?re:ie]);let l=c&&t[c];if(l)for(let e=0;efunction(t,n){t[Ie]=!0,e&&e.apply(t,n)})}function He(e,t){t.patchMethod(e,`queueMicrotask`,e=>function(e,t){Zone.current.scheduleMicroTask(`queueMicrotask`,t[0])})}var Ue=ce(`zoneTask`);function We(e,t,n,r){let i=null,a=null;t+=r,n+=r;let o={};function s(t){let n=t.data;n.args[0]=function(){return t.invoke.apply(this,arguments)};let r=i.apply(e,n.args);return je(r)?n.handleId=r:(n.handle=r,n.isRefreshable=Ae(r?.refresh)),t}function c(t){let{handle:n,handleId:r}=t.data;return a.call(e,n??r)}i=De(e,t,n=>function(i,a){if(Ae(a[0])){let e={isRefreshable:!1,isPeriodic:r===`Interval`,delay:r===`Timeout`||r===`Interval`?a[1]||0:void 0,args:a},n=a[0];a[0]=function(){try{return n.apply(this,arguments)}finally{let{handle:t,handleId:n,isPeriodic:r,isRefreshable:i}=e;!r&&!i&&(n?delete o[n]:t&&(t[Ue]=null))}};let i=se(t,a[0],e,s,c);if(!i)return i;let{handleId:l,handle:u,isRefreshable:d,isPeriodic:f}=i.data;if(l)o[l]=i;else if(u&&(u[Ue]=i,d&&!f)){let e=u.refresh;u.refresh=function(){let{zone:t,state:n}=i;return n===`notScheduled`?(i._state=`scheduled`,t._updateTaskCount(i,1)):n===`running`&&(i._state=`scheduling`),e.call(this)}}return u??l??i}return n.apply(e,a)}),a=De(e,n,t=>function(n,r){let i=r[0],a;je(i)?(a=o[i],delete o[i]):(a=i?.[Ue],a?i[Ue]=null:a=i),a?.type?a.cancelFn&&a.zone.cancelTask(a):t.apply(e,r)})}function Ge(e,t){let{isBrowser:n,isMix:r}=t.getGlobalObjects();(n||r)&&e.customElements&&`customElements`in e&&t.patchCallbacks(t,e.customElements,`customElements`,`define`,[`connectedCallback`,`disconnectedCallback`,`adoptedCallback`,`attributeChangedCallback`,`formAssociatedCallback`,`formDisabledCallback`,`formResetCallback`,`formStateRestoreCallback`])}function Ke(e,t){if(Zone[t.symbol(`patchEventTarget`)])return;let{eventNames:n,zoneSymbolEventNames:r,TRUE_STR:i,FALSE_STR:a,ZONE_SYMBOL_PREFIX:o}=t.getGlobalObjects();for(let e=0;et.target===e);if(r.length===0)return t;let i=r[0].ignoreProperties;return t.filter(e=>i.indexOf(e)===-1)}function Ye(e,t,n,r){e&&we(e,Je(e,t,n),r)}function Xe(e){return Object.getOwnPropertyNames(e).filter(e=>e.startsWith(`on`)&&e.length>2).map(e=>e.substring(2))}function eee(e,t){if(_e&&!ye||Zone[e.symbol(`patchEvents`)])return;let n=t.__Zone_ignore_on_properties,r=[];if(ve){let e=window;r=r.concat([`Document`,`SVGElement`,`Element`,`HTMLElement`,`HTMLBodyElement`,`HTMLMediaElement`,`HTMLFrameSetElement`,`HTMLFrameElement`,`HTMLIFrameElement`,`HTMLMarqueeElement`,`Worker`]),Ye(e,Xe(e),n,p(e))}r=r.concat([`XMLHttpRequest`,`XMLHttpRequestEventTarget`,`IDBIndex`,`IDBRequest`,`IDBOpenDBRequest`,`IDBDatabase`,`IDBTransaction`,`IDBCursor`,`WebSocket`]);for(let e=0;e{let t=`clear`;We(e,`set`,t,`Timeout`),We(e,`set`,t,`Interval`),We(e,`set`,t,`Immediate`)}),e.__load_patch(`requestAnimationFrame`,e=>{We(e,`request`,`cancel`,`AnimationFrame`),We(e,`mozRequest`,`mozCancel`,`AnimationFrame`),We(e,`webkitRequest`,`webkitCancel`,`AnimationFrame`)}),e.__load_patch(`blocking`,(e,t)=>{let n=[`alert`,`prompt`,`confirm`];for(let r=0;rfunction(r,a){return t.current.run(n,e,a,i)})}}),e.__load_patch(`EventTarget`,(e,t,n)=>{qe(e,n),Ke(e,n);let r=e.XMLHttpRequestEventTarget;r&&r.prototype&&n.patchEventTarget(e,n,[r.prototype])}),e.__load_patch(`MutationObserver`,(e,t,n)=>{Ee(`MutationObserver`),Ee(`WebKitMutationObserver`)}),e.__load_patch(`IntersectionObserver`,(e,t,n)=>{Ee(`IntersectionObserver`)}),e.__load_patch(`FileReader`,(e,t,n)=>{Ee(`FileReader`)}),e.__load_patch(`on_property`,(e,t,n)=>{eee(n,e)}),e.__load_patch(`customElements`,(e,t,n)=>{Ge(e,n)}),e.__load_patch(`XHR`,(e,t)=>{c(e);let n=ce(`xhrTask`),r=ce(`xhrSync`),i=ce(`xhrListener`),a=ce(`xhrScheduled`),o=ce(`xhrURL`),s=ce(`xhrErrorBeforeScheduled`);function c(e){let c=e.XMLHttpRequest;if(!c)return;let l=c.prototype;function u(e){return e[n]}let d=l[te],f=l[ne];if(!d){let t=e.XMLHttpRequestEventTarget;if(t){let e=t.prototype;d=e[te],f=e[ne]}}let p=`readystatechange`,m=`scheduled`;function ee(e){let r=e.data,o=r.target;o[a]=!1,o[s]=!1;let c=o[i];d||(d=o[te],f=o[ne]),c&&f.call(o,p,c);let l=o[i]=()=>{if(o.readyState===o.DONE){if(!r.aborted&&o[a]&&e.state===m){let n=o[t.__symbol__(`loadfalse`)];if(o.status!==0&&n&&n.length>0){let i=e.invoke;e.invoke=function(){let n=o[t.__symbol__(`loadfalse`)];for(let t=0;tfunction(e,t){return e[r]=t[2]==0,e[o]=t[1],re.apply(e,t)}),ie=ce(`fetchTaskAborting`),ae=ce(`fetchTaskScheduling`),oe=De(l,`send`,()=>function(e,n){if(t.current[ae]===!0||e[r])return oe.apply(e,n);{let t={target:e,url:e[o],isPeriodic:!1,args:n,aborted:!1},r=se(`XMLHttpRequest.send`,h,t,ee,g);e&&e[s]===!0&&!t.aborted&&r.state===m&&r.invoke()}}),le=De(l,`abort`,()=>function(e,n){let r=u(e);if(r&&typeof r.type==`string`){if(r.cancelFn==null||r.data&&r.data.aborted)return;r.zone.cancelTask(r)}else if(t.current[ie]===!0)return le.apply(e,n)})}}),e.__load_patch(`geolocation`,e=>{e.navigator&&e.navigator.geolocation&&me(e.navigator.geolocation,[`getCurrentPosition`,`watchPosition`])}),e.__load_patch(`PromiseRejectionEvent`,(e,t)=>{function n(t){return function(n){Be(e,t).forEach(r=>{let i=e.PromiseRejectionEvent;if(i){let e=new i(t,{promise:n.promise,reason:n.rejection});r.invoke(e)}})}}e.PromiseRejectionEvent&&(t[ce(`unhandledPromiseRejectionHandler`)]=n(`unhandledrejection`),t[ce(`rejectionHandledHandler`)]=n(`rejectionhandled`))}),e.__load_patch(`queueMicrotask`,(e,t,n)=>{He(e,n)})}function Ze(e){e.__load_patch(`ZoneAwarePromise`,(e,t,n)=>{let r=Object.getOwnPropertyDescriptor,i=Object.defineProperty;function a(e){return e&&e.toString===Object.prototype.toString?(e.constructor&&e.constructor.name||``)+`: `+JSON.stringify(e):e?e.toString():Object.prototype.toString.call(e)}let o=n.symbol,s=[],c=e[o(`DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION`)]!==!1,l=o(`Promise`),u=o(`then`);n.onUnhandledError=e=>{if(n.showUncaughtError()){let t=e&&e.rejection;t&&e.zone&&e.task?console.error(`Unhandled Promise rejection:`,t instanceof Error?t.message:t,`; Zone:`,e.zone.name,`; Task:`,e.task&&e.task.source,`; Value:`,t,t instanceof Error?t.stack:void 0):console.error(e)}},n.microtaskDrainDone=()=>{for(;s.length;){let e=s.shift();try{e.zone.runGuarded(()=>{throw e.throwOriginal?e.rejection:e})}catch(e){f(e)}}};let d=o(`unhandledPromiseRejectionHandler`);function f(e){n.onUnhandledError(e);try{let n=t[d];typeof n==`function`&&n.call(this,e)}catch{}}function p(e){return e&&typeof e.then==`function`}function m(e){return e}function ee(e){return pe.reject(e)}let h=o(`state`),g=o(`value`),te=o(`finally`),ne=o(`parentPromiseValue`),re=o(`parentPromiseState`);function ie(e,t){return n=>{try{se(e,t,n)}catch(t){se(e,!1,t)}}}let ae=function(){let e=!1;return function(t){return function(){e||(e=!0,t.apply(null,arguments))}}},oe=o(`currentTaskTrace`);function se(e,r,o){let l=ae();if(e===o)throw TypeError(`Promise resolved with itself`);if(e[h]===null){let u=null;try{(typeof o==`object`||typeof o==`function`)&&(u=o&&o.then)}catch(t){return l(()=>{se(e,!1,t)})(),e}if(r!==!1&&o instanceof pe&&Object.hasOwn(o,h)&&Object.hasOwn(o,g)&&o[h]!==null)le(o),se(e,o[h],o[g]);else if(r!==!1&&typeof u==`function`)try{u.call(o,l(ie(e,r)),l(ie(e,!1)))}catch(t){l(()=>{se(e,!1,t)})()}else{e[h]=r;let l=e[g];if(e[g]=o,e[te]===te&&r===!0&&(e[h]=e[re],e[g]=e[ne]),r===!1&&o instanceof Error){let e=t.currentTask&&t.currentTask.data&&t.currentTask.data.__creationTrace__;e&&i(o,oe,{configurable:!0,enumerable:!1,writable:!0,value:e})}for(let t=0;t{try{let r=e[g],i=!!n&&te===n[te];i&&(n[ne]=r,n[re]=a),se(n,!0,t.run(o,void 0,i&&o!==ee&&o!==m?[]:[r]))}catch(e){se(n,!1,e)}},n)}let de=function(){},fe=e.AggregateError;class pe{static toString(){return`function ZoneAwarePromise() { [native code] }`}static resolve(e){return e instanceof pe?e:se(new this(null),!0,e)}static reject(e){return se(new this(null),!1,e)}static withResolvers(){let e={};return e.promise=new pe((t,n)=>{e.resolve=t,e.reject=n}),e}static any(e){if(!e||typeof e[Symbol.iterator]!=`function`)return Promise.reject(new fe([],`All promises were rejected`));let t=[],n=0;try{for(let r of e)n++,t.push(pe.resolve(r))}catch{return Promise.reject(new fe([],`All promises were rejected`))}if(n===0)return Promise.reject(new fe([],`All promises were rejected`));let r=!1,i=[];return new pe((e,a)=>{for(let o=0;o{r||(r=!0,e(t))},e=>{i.push(e),n--,n===0&&(r=!0,a(new fe(i,`All promises were rejected`)))})})}static race(e){let t,n,r=new this((e,r)=>{t=e,n=r});function i(e){t(e)}function a(e){n(e)}for(let t of e)p(t)||(t=this.resolve(t)),t.then(i,a);return r}static all(e){return pe.allWithCallback(e)}static allSettled(e){return(this&&this.prototype instanceof pe?this:pe).allWithCallback(e,{thenCallback:e=>({status:`fulfilled`,value:e}),errorCallback:e=>({status:`rejected`,reason:e})})}static allWithCallback(e,t){let n,r,i=new this((e,t)=>{n=e,r=t}),a=2,o=0,s=[];for(let i of e){p(i)||(i=this.resolve(i));let e=o;try{i.then(r=>{s[e]=t?t.thenCallback(r):r,a--,a===0&&n(s)},i=>{t?(s[e]=t.errorCallback(i),a--,a===0&&n(s)):r(i)})}catch(e){r(e)}a++,o++}return a-=2,a===0&&n(s),i}constructor(e){let t=this;if(!(t instanceof pe))throw Error(`Must be an instanceof Promise.`);t[h]=null,t[g]=[];try{let n=ae();e&&e(n(ie(t,!0)),n(ie(t,!1)))}catch(e){se(t,!1,e)}}get[Symbol.toStringTag](){return`Promise`}get[Symbol.species](){return pe}then(e,n){let r=this.constructor?.[Symbol.species];(!r||typeof r!=`function`)&&(r=this.constructor||pe);let i=new r(de),a=t.current;return this[h]==null?this[g].push(a,i,e,n):ue(this,a,i,e,n),i}catch(e){return this.then(null,e)}finally(e){let n=this.constructor?.[Symbol.species];(!n||typeof n!=`function`)&&(n=pe);let r=new n(de);r[te]=te;let i=t.current;return this[h]==null?this[g].push(i,r,e,e):ue(this,i,r,e,e),r}}pe.resolve=pe.resolve,pe.reject=pe.reject,pe.race=pe.race,pe.all=pe.all;let me=e[l]=e.Promise;e.Promise=pe;let he=o(`thenPatched`);function ge(e){let t=e.prototype,n=r(t,`then`);if(n&&(n.writable===!1||!n.configurable))return;let i=t.then;t[u]=i,e.prototype.then=function(e,t){return new pe((e,t)=>{i.call(this,e,t)}).then(e,t)},e[he]=!0}n.patchThen=ge;function _e(e){return function(t,n){let r=e.apply(t,n);if(r instanceof pe)return r;let i=r.constructor;return i[he]||ge(i),r}}if(me){ge(me);let t=me.try;t&&typeof t==`function`&&(pe.try=t),De(e,`fetch`,e=>_e(e))}return Promise[t.__symbol__(`uncaughtPromiseErrors`)]=s,pe})}function Qe(e){e.__load_patch(`toString`,e=>{let t=Function.prototype.toString,n=ce(`OriginalDelegate`),r=ce(`Promise`),i=ce(`Error`),a=function(){if(typeof this==`function`){let a=this[n];if(a)return typeof a==`function`?t.call(a):Object.prototype.toString.call(a);if(this===Promise){let n=e[r];if(n)return t.call(n)}if(this===Error){let n=e[i];if(n)return t.call(n)}}return t.call(this)};a[n]=t,Function.prototype.toString=a;let o=Object.prototype.toString;Object.prototype.toString=function(){return typeof Promise==`function`&&this instanceof Promise?`[object Promise]`:o.call(this)}})}function $e(e,t,n,r,i){let a=Zone.__symbol__(r);if(t[a])return;let o=t[a]=t[r];t[r]=function(a,s,c){return s&&s.prototype&&i.forEach(function(t){let i=`${n}.${r}::`+t,a=s.prototype;try{if(Object.hasOwn(a,t)){let n=e.ObjectGetOwnPropertyDescriptor(a,t);n&&n.value?(n.value=e.wrapWithCurrentZone(n.value,i),e._redefineProperty(s.prototype,t,n)):a[t]&&(a[t]=e.wrapWithCurrentZone(a[t],i))}else a[t]&&(a[t]=e.wrapWithCurrentZone(a[t],i))}catch{}}),o.call(t,a,s,c)},e.attachOriginToPatched(t[r],o)}function et(e){e.__load_patch(`util`,(e,t,n)=>{let r=Xe(e);n.patchOnProperties=we,n.patchMethod=De,n.bindArguments=pe,n.patchMacroTask=Oe;let i=t.__symbol__(`BLACK_LISTED_EVENTS`),a=t.__symbol__(`UNPATCHED_EVENTS`);e[a]&&(e[i]=e[a]),e[i]&&(t[i]=t[a]=e[i]),n.patchEventPrototype=Ve,n.patchEventTarget=ze,n.ObjectDefineProperty=f,n.ObjectGetOwnPropertyDescriptor=d,n.ObjectCreate=m,n.ArraySlice=ee,n.patchClass=Ee,n.wrapWithCurrentZone=oe,n.filterProperties=Je,n.attachOriginToPatched=ke,n._redefineProperty=Object.defineProperty,n.patchCallbacks=$e,n.getGlobalObjects=()=>({globalSources:Pe,zoneSymbolEventNames:Ne,eventNames:r,isBrowser:ve,isMix:ye,isNode:_e,TRUE_STR:re,FALSE_STR:ie,ZONE_SYMBOL_PREFIX:ae,ADD_EVENT_LISTENER_STR:h,REMOVE_EVENT_LISTENER_STR:g})})}function tt(e){Ze(e),Qe(e),et(e)}var nt=u();tt(nt),tee(nt);var rt=(function(e){return e[e.NONE=0]=`NONE`,e[e.HTML=1]=`HTML`,e[e.STYLE=2]=`STYLE`,e[e.SCRIPT=3]=`SCRIPT`,e[e.URL=4]=`URL`,e[e.RESOURCE_URL=5]=`RESOURCE_URL`,e[e.ATTRIBUTE_NO_BINDING=6]=`ATTRIBUTE_NO_BINDING`,e})(rt||{}),it=(function(e){return e[e.None=0]=`None`,e[e.Const=1]=`Const`,e})(it||{}),at=class{modifiers;constructor(e=it.None){this.modifiers=e}hasModifier(e){return(this.modifiers&e)!==0}},ot=(function(e){return e[e.Dynamic=0]=`Dynamic`,e[e.Bool=1]=`Bool`,e[e.String=2]=`String`,e[e.Int=3]=`Int`,e[e.Number=4]=`Number`,e[e.Function=5]=`Function`,e[e.Inferred=6]=`Inferred`,e[e.None=7]=`None`,e})(ot||{}),st=class extends at{name;constructor(e,t){super(t),this.name=e}visitType(e,t){return e.visitBuiltinType(this,t)}};ot.Dynamic;var ct=new st(ot.Inferred);ot.Bool,ot.Int,ot.Number,ot.String,ot.Function,ot.None;var _=(function(e){return e[e.Equals=0]=`Equals`,e[e.NotEquals=1]=`NotEquals`,e[e.Assign=2]=`Assign`,e[e.Identical=3]=`Identical`,e[e.NotIdentical=4]=`NotIdentical`,e[e.Minus=5]=`Minus`,e[e.Plus=6]=`Plus`,e[e.Divide=7]=`Divide`,e[e.Multiply=8]=`Multiply`,e[e.Modulo=9]=`Modulo`,e[e.And=10]=`And`,e[e.Or=11]=`Or`,e[e.BitwiseOr=12]=`BitwiseOr`,e[e.BitwiseAnd=13]=`BitwiseAnd`,e[e.Lower=14]=`Lower`,e[e.LowerEquals=15]=`LowerEquals`,e[e.Bigger=16]=`Bigger`,e[e.BiggerEquals=17]=`BiggerEquals`,e[e.NullishCoalesce=18]=`NullishCoalesce`,e[e.Exponentiation=19]=`Exponentiation`,e[e.In=20]=`In`,e[e.InstanceOf=21]=`InstanceOf`,e[e.AdditionAssignment=22]=`AdditionAssignment`,e[e.SubtractionAssignment=23]=`SubtractionAssignment`,e[e.MultiplicationAssignment=24]=`MultiplicationAssignment`,e[e.DivisionAssignment=25]=`DivisionAssignment`,e[e.RemainderAssignment=26]=`RemainderAssignment`,e[e.ExponentiationAssignment=27]=`ExponentiationAssignment`,e[e.AndAssignment=28]=`AndAssignment`,e[e.OrAssignment=29]=`OrAssignment`,e[e.NullishCoalesceAssignment=30]=`NullishCoalesceAssignment`,e})(_||{});function lt(e,t){return e==null||t==null?e==t:e.isEquivalent(t)}function ut(e,t,n){let r=e.length;if(r!==t.length)return!1;for(let i=0;ie.isEquivalent(t))}var ft=class{leadingComments;type;sourceSpan;constructor(e,t,n){this.leadingComments=n,this.type=e||null,this.sourceSpan=t||null}prop(e,t){return new St(this,e,null,t)}key(e,t,n){return new nee(this,e,t,n)}callFn(e,t,n,r){return new ht(this,e,null,t,n,r)}instantiate(e,t,n,r){return new gt(this,e,t,n)}conditional(e,t=null,n,r){return new bt(this,e,t,null,n)}equals(e,t){return new xt(_.Equals,this,e,null,t)}notEquals(e,t){return new xt(_.NotEquals,this,e,null,t)}identical(e,t){return new xt(_.Identical,this,e,null,t)}notIdentical(e,t){return new xt(_.NotIdentical,this,e,null,t)}minus(e,t){return new xt(_.Minus,this,e,null,t)}plus(e,t){return new xt(_.Plus,this,e,null,t)}divide(e,t){return new xt(_.Divide,this,e,null,t)}multiply(e,t){return new xt(_.Multiply,this,e,null,t)}modulo(e,t){return new xt(_.Modulo,this,e,null,t)}power(e,t){return new xt(_.Exponentiation,this,e,null,t)}and(e,t){return new xt(_.And,this,e,null,t)}bitwiseOr(e,t){return new xt(_.BitwiseOr,this,e,null,t)}bitwiseAnd(e,t){return new xt(_.BitwiseAnd,this,e,null,t)}or(e,t){return new xt(_.Or,this,e,null,t)}lower(e,t){return new xt(_.Lower,this,e,null,t)}lowerEquals(e,t){return new xt(_.LowerEquals,this,e,null,t)}bigger(e,t){return new xt(_.Bigger,this,e,null,t)}biggerEquals(e,t){return new xt(_.BiggerEquals,this,e,null,t)}isBlank(e){return this.equals(see,e)}nullishCoalesce(e,t){return new xt(_.NullishCoalesce,this,e,null,t)}toStmt(e){return new lee(this,null,e)}},pt=class e extends ft{name;constructor(e,t,n,r){super(t,n,r),this.name=e}isEquivalent(t){return t instanceof e&&this.name===t.name}isConstant(){return!1}visitExpression(e,t){return e.visitReadVarExpr(this,t)}clone(){return new e(this.name,this.type,this.sourceSpan)}set(e){return new xt(_.Assign,this,e,null,this.sourceSpan)}},mt=class e extends ft{expr;constructor(e,t,n,r){super(t,n,r),this.expr=e}visitExpression(e,t){return e.visitTypeofExpr(this,t)}isEquivalent(t){return t instanceof e&&t.expr.isEquivalent(this.expr)}isConstant(){return this.expr.isConstant()}clone(){return new e(this.expr.clone())}},ht=class e extends ft{fn;args;pure;isOptional;constructor(e,t,n,r,i=!1,a,o=!1){super(n,r,a),this.fn=e,this.args=t,this.pure=i,this.isOptional=o}get receiver(){return this.fn}isEquivalent(t){return t instanceof e&&this.fn.isEquivalent(t.fn)&&dt(this.args,t.args)&&this.pure===t.pure}isConstant(){return!1}visitExpression(e,t){return e.visitInvokeFunctionExpr(this,t)}clone(){return new e(this.fn.clone(),this.args.map(e=>e.clone()),this.type,this.sourceSpan,this.pure,[],this.isOptional)}},gt=class e extends ft{classExpr;args;constructor(e,t,n,r,i){super(n,r,i),this.classExpr=e,this.args=t}isEquivalent(t){return t instanceof e&&this.classExpr.isEquivalent(t.classExpr)&&dt(this.args,t.args)}isConstant(){return!1}visitExpression(e,t){return e.visitInstantiateExpr(this,t)}clone(){return new e(this.classExpr.clone(),this.args.map(e=>e.clone()),this.type,this.sourceSpan)}},_t=class e extends ft{body;flags;constructor(e,t,n,r){super(null,n,r),this.body=e,this.flags=t}isEquivalent(t){return t instanceof e&&this.body===t.body&&this.flags===t.flags}isConstant(){return!0}visitExpression(e,t){return e.visitRegularExpressionLiteral(this,t)}clone(){return new e(this.body,this.flags,this.sourceSpan)}},vt=class e extends ft{value;constructor(e,t,n,r){super(t,n,r),this.value=e}isEquivalent(t){return t instanceof e&&this.value===t.value}isConstant(){return!0}visitExpression(e,t){return e.visitLiteralExpr(this,t)}clone(){return new e(this.value,this.type,this.sourceSpan)}},yt=class e extends ft{value;typeParams;constructor(e,t,n=null,r,i){super(t,r,i),this.value=e,this.typeParams=n}isEquivalent(t){return t instanceof e&&this.value.name===t.value.name&&this.value.moduleName===t.value.moduleName}isConstant(){return!1}visitExpression(e,t){return e.visitExternalExpr(this,t)}clone(){return new e(this.value,this.type,this.typeParams,this.sourceSpan)}},bt=class e extends ft{condition;falseCase;trueCase;constructor(e,t,n=null,r,i,a){super(r||t.type,i,a),this.condition=e,this.falseCase=n,this.trueCase=t}isEquivalent(t){return t instanceof e&&this.condition.isEquivalent(t.condition)&&this.trueCase.isEquivalent(t.trueCase)&<(this.falseCase,t.falseCase)}isConstant(){return!1}visitExpression(e,t){return e.visitConditionalExpr(this,t)}clone(){return new e(this.condition.clone(),this.trueCase.clone(),this.falseCase?.clone(),this.type,this.sourceSpan)}},xt=class e extends ft{operator;rhs;lhs;constructor(e,t,n,r,i,a){super(r||t.type,i,a),this.operator=e,this.rhs=n,this.lhs=t}isEquivalent(t){return t instanceof e&&this.operator===t.operator&&this.lhs.isEquivalent(t.lhs)&&this.rhs.isEquivalent(t.rhs)}isConstant(){return!1}visitExpression(e,t){return e.visitBinaryOperatorExpr(this,t)}clone(){return new e(this.operator,this.lhs.clone(),this.rhs.clone(),this.type,this.sourceSpan)}isAssignment(){let e=this.operator;return e===_.Assign||e===_.AdditionAssignment||e===_.SubtractionAssignment||e===_.MultiplicationAssignment||e===_.DivisionAssignment||e===_.RemainderAssignment||e===_.ExponentiationAssignment||e===_.AndAssignment||e===_.OrAssignment||e===_.NullishCoalesceAssignment}},St=class e extends ft{receiver;name;isOptional;constructor(e,t,n,r,i,a=!1){super(n,r,i),this.receiver=e,this.name=t,this.isOptional=a}get index(){return this.name}isEquivalent(t){return t instanceof e&&this.receiver.isEquivalent(t.receiver)&&this.name===t.name&&this.isOptional===t.isOptional}isConstant(){return!1}visitExpression(e,t){return e.visitReadPropExpr(this,t)}set(e){return new xt(_.Assign,this.receiver.prop(this.name),e,null,this.sourceSpan)}clone(){return new e(this.receiver.clone(),this.name,this.type,this.sourceSpan,[],this.isOptional)}},nee=class e extends ft{receiver;index;isOptional;constructor(e,t,n,r,i,a=!1){super(n,r,i),this.receiver=e,this.index=t,this.isOptional=a}isEquivalent(t){return t instanceof e&&this.receiver.isEquivalent(t.receiver)&&this.index.isEquivalent(t.index)&&this.isOptional===t.isOptional}isConstant(){return!1}visitExpression(e,t){return e.visitReadKeyExpr(this,t)}set(e){return new xt(_.Assign,this.receiver.key(this.index),e,null,this.sourceSpan)}clone(){return new e(this.receiver.clone(),this.index.clone(),this.type,this.sourceSpan,[],this.isOptional)}},ree=class e extends ft{entries;constructor(e,t,n,r){super(t,n,r),this.entries=e}isConstant(){return this.entries.every(e=>e.isConstant())}isEquivalent(t){return t instanceof e&&dt(this.entries,t.entries)}visitExpression(e,t){return e.visitLiteralArrayExpr(this,t)}clone(){return new e(this.entries.map(e=>e.clone()),this.type,this.sourceSpan)}},iee=class e{expression;constructor(e){this.expression=e}isEquivalent(t){return t instanceof e&&this.expression.isEquivalent(t.expression)}clone(){return new e(this.expression.clone())}isConstant(){return this.expression.isConstant()}},aee=class e extends ft{entries;valueType=null;constructor(e,t,n,r){super(t,n,r),this.entries=e,t&&(this.valueType=t.valueType)}isEquivalent(t){return t instanceof e&&dt(this.entries,t.entries)}isConstant(){return this.entries.every(e=>e.isConstant())}visitExpression(e,t){return e.visitLiteralMapExpr(this,t)}clone(){let t=this.entries.map(e=>e.clone());return new e(t,this.type,this.sourceSpan)}},oee=class e extends ft{expression;constructor(e,t,n){super(null,t,n),this.expression=e}isEquivalent(t){return t instanceof e&&this.expression.isEquivalent(t.expression)}isConstant(){return this.expression.isConstant()}visitExpression(e,t){return e.visitSpreadElementExpr(this,t)}clone(){return new e(this.expression.clone(),this.sourceSpan)}},see=new vt(null,ct,null),Ct=(function(e){return e[e.None=0]=`None`,e[e.Final=1]=`Final`,e[e.Private=2]=`Private`,e[e.Exported=4]=`Exported`,e[e.Static=8]=`Static`,e})(Ct||{}),cee=class{modifiers;sourceSpan;leadingComments;constructor(e=Ct.None,t=null,n){this.modifiers=e,this.sourceSpan=t,this.leadingComments=n}hasModifier(e){return(this.modifiers&e)!==0}addLeadingComment(e){this.leadingComments=this.leadingComments??[],this.leadingComments.push(e)}},lee=class e extends cee{expr;constructor(e,t,n){super(Ct.None,t,n),this.expr=e}isEquivalent(t){return t instanceof e&&this.expr.isEquivalent(t.expr)}visitStatement(e,t){return e.visitExpressionStmt(this,t)}};(class e{static INSTANCE=new e;keyOf(e){if(e instanceof vt&&typeof e.value==`string`)return`"${e.value}"`;if(e instanceof vt)return String(e.value);if(e instanceof _t)return`/${e.body}/${e.flags??``}`;if(e instanceof ree){let t=[];for(let n of e.entries)t.push(this.keyOf(n));return`[${t.join(`,`)}]`}if(e instanceof aee){let t=[];for(let n of e.entries)if(n instanceof iee)t.push(`...`+this.keyOf(n.expression));else{let e=n.key;n.quoted&&(e=`"${e}"`),t.push(e+`:`+this.keyOf(n.value))}return`{${t.join(`,`)}}`}if(e instanceof yt)return`import("${e.value.moduleName}", ${e.value.name})`;if(e instanceof pt)return`read(${e.name})`;if(e instanceof mt)return`typeof(${this.keyOf(e.expr)})`;if(e instanceof oee)return`...${this.keyOf(e.expression)}`;throw Error(`${this.constructor.name} does not handle expressions of type ${e.constructor.name}`)}});var v=`@angular/core`,y=(()=>{class e{static core={name:null,moduleName:v};static namespaceHTML={name:`ɵɵnamespaceHTML`,moduleName:v};static namespaceMathML={name:`ɵɵnamespaceMathML`,moduleName:v};static namespaceSVG={name:`ɵɵnamespaceSVG`,moduleName:v};static element={name:`ɵɵelement`,moduleName:v};static elementStart={name:`ɵɵelementStart`,moduleName:v};static elementEnd={name:`ɵɵelementEnd`,moduleName:v};static foreignComponent={name:`ɵɵforeignComponent`,moduleName:v};static foreignContent={name:`ɵɵforeignContent`,moduleName:v};static foreignContentFn={name:`ɵɵforeignContentFn`,moduleName:v};static domElement={name:`ɵɵdomElement`,moduleName:v};static domElementStart={name:`ɵɵdomElementStart`,moduleName:v};static domElementEnd={name:`ɵɵdomElementEnd`,moduleName:v};static domElementContainer={name:`ɵɵdomElementContainer`,moduleName:v};static domElementContainerStart={name:`ɵɵdomElementContainerStart`,moduleName:v};static domElementContainerEnd={name:`ɵɵdomElementContainerEnd`,moduleName:v};static domTemplate={name:`ɵɵdomTemplate`,moduleName:v};static domListener={name:`ɵɵdomListener`,moduleName:v};static advance={name:`ɵɵadvance`,moduleName:v};static syntheticHostProperty={name:`ɵɵsyntheticHostProperty`,moduleName:v};static syntheticHostListener={name:`ɵɵsyntheticHostListener`,moduleName:v};static attribute={name:`ɵɵattribute`,moduleName:v};static classProp={name:`ɵɵclassProp`,moduleName:v};static elementContainerStart={name:`ɵɵelementContainerStart`,moduleName:v};static elementContainerEnd={name:`ɵɵelementContainerEnd`,moduleName:v};static elementContainer={name:`ɵɵelementContainer`,moduleName:v};static styleMap={name:`ɵɵstyleMap`,moduleName:v};static classMap={name:`ɵɵclassMap`,moduleName:v};static styleProp={name:`ɵɵstyleProp`,moduleName:v};static interpolate={name:`ɵɵinterpolate`,moduleName:v};static interpolate1={name:`ɵɵinterpolate1`,moduleName:v};static interpolate2={name:`ɵɵinterpolate2`,moduleName:v};static interpolate3={name:`ɵɵinterpolate3`,moduleName:v};static interpolate4={name:`ɵɵinterpolate4`,moduleName:v};static interpolate5={name:`ɵɵinterpolate5`,moduleName:v};static interpolate6={name:`ɵɵinterpolate6`,moduleName:v};static interpolate7={name:`ɵɵinterpolate7`,moduleName:v};static interpolate8={name:`ɵɵinterpolate8`,moduleName:v};static interpolateV={name:`ɵɵinterpolateV`,moduleName:v};static nextContext={name:`ɵɵnextContext`,moduleName:v};static resetView={name:`ɵɵresetView`,moduleName:v};static templateCreate={name:`ɵɵtemplate`,moduleName:v};static defer={name:`ɵɵdefer`,moduleName:v};static deferWhen={name:`ɵɵdeferWhen`,moduleName:v};static deferOnIdle={name:`ɵɵdeferOnIdle`,moduleName:v};static deferOnImmediate={name:`ɵɵdeferOnImmediate`,moduleName:v};static deferOnTimer={name:`ɵɵdeferOnTimer`,moduleName:v};static deferOnHover={name:`ɵɵdeferOnHover`,moduleName:v};static deferOnInteraction={name:`ɵɵdeferOnInteraction`,moduleName:v};static deferOnViewport={name:`ɵɵdeferOnViewport`,moduleName:v};static deferPrefetchWhen={name:`ɵɵdeferPrefetchWhen`,moduleName:v};static deferPrefetchOnIdle={name:`ɵɵdeferPrefetchOnIdle`,moduleName:v};static deferPrefetchOnImmediate={name:`ɵɵdeferPrefetchOnImmediate`,moduleName:v};static deferPrefetchOnTimer={name:`ɵɵdeferPrefetchOnTimer`,moduleName:v};static deferPrefetchOnHover={name:`ɵɵdeferPrefetchOnHover`,moduleName:v};static deferPrefetchOnInteraction={name:`ɵɵdeferPrefetchOnInteraction`,moduleName:v};static deferPrefetchOnViewport={name:`ɵɵdeferPrefetchOnViewport`,moduleName:v};static deferHydrateWhen={name:`ɵɵdeferHydrateWhen`,moduleName:v};static deferHydrateNever={name:`ɵɵdeferHydrateNever`,moduleName:v};static deferHydrateOnIdle={name:`ɵɵdeferHydrateOnIdle`,moduleName:v};static deferHydrateOnImmediate={name:`ɵɵdeferHydrateOnImmediate`,moduleName:v};static deferHydrateOnTimer={name:`ɵɵdeferHydrateOnTimer`,moduleName:v};static deferHydrateOnHover={name:`ɵɵdeferHydrateOnHover`,moduleName:v};static deferHydrateOnInteraction={name:`ɵɵdeferHydrateOnInteraction`,moduleName:v};static deferHydrateOnViewport={name:`ɵɵdeferHydrateOnViewport`,moduleName:v};static deferEnableTimerScheduling={name:`ɵɵdeferEnableTimerScheduling`,moduleName:v};static enableIncrementalHydrationRuntime={name:`ɵɵenableIncrementalHydrationRuntime`,moduleName:v};static conditionalCreate={name:`ɵɵconditionalCreate`,moduleName:v};static conditionalBranchCreate={name:`ɵɵconditionalBranchCreate`,moduleName:v};static conditional={name:`ɵɵconditional`,moduleName:v};static repeater={name:`ɵɵrepeater`,moduleName:v};static repeaterCreate={name:`ɵɵrepeaterCreate`,moduleName:v};static repeaterTrackByIndex={name:`ɵɵrepeaterTrackByIndex`,moduleName:v};static repeaterTrackByIdentity={name:`ɵɵrepeaterTrackByIdentity`,moduleName:v};static componentInstance={name:`ɵɵcomponentInstance`,moduleName:v};static text={name:`ɵɵtext`,moduleName:v};static enableBindings={name:`ɵɵenableBindings`,moduleName:v};static disableBindings={name:`ɵɵdisableBindings`,moduleName:v};static getCurrentView={name:`ɵɵgetCurrentView`,moduleName:v};static textInterpolate={name:`ɵɵtextInterpolate`,moduleName:v};static textInterpolate1={name:`ɵɵtextInterpolate1`,moduleName:v};static textInterpolate2={name:`ɵɵtextInterpolate2`,moduleName:v};static textInterpolate3={name:`ɵɵtextInterpolate3`,moduleName:v};static textInterpolate4={name:`ɵɵtextInterpolate4`,moduleName:v};static textInterpolate5={name:`ɵɵtextInterpolate5`,moduleName:v};static textInterpolate6={name:`ɵɵtextInterpolate6`,moduleName:v};static textInterpolate7={name:`ɵɵtextInterpolate7`,moduleName:v};static textInterpolate8={name:`ɵɵtextInterpolate8`,moduleName:v};static textInterpolateV={name:`ɵɵtextInterpolateV`,moduleName:v};static restoreView={name:`ɵɵrestoreView`,moduleName:v};static pureFunction0={name:`ɵɵpureFunction0`,moduleName:v};static pureFunction1={name:`ɵɵpureFunction1`,moduleName:v};static pureFunction2={name:`ɵɵpureFunction2`,moduleName:v};static pureFunction3={name:`ɵɵpureFunction3`,moduleName:v};static pureFunction4={name:`ɵɵpureFunction4`,moduleName:v};static pureFunction5={name:`ɵɵpureFunction5`,moduleName:v};static pureFunction6={name:`ɵɵpureFunction6`,moduleName:v};static pureFunction7={name:`ɵɵpureFunction7`,moduleName:v};static pureFunction8={name:`ɵɵpureFunction8`,moduleName:v};static pureFunctionV={name:`ɵɵpureFunctionV`,moduleName:v};static pipeBind1={name:`ɵɵpipeBind1`,moduleName:v};static pipeBind2={name:`ɵɵpipeBind2`,moduleName:v};static pipeBind3={name:`ɵɵpipeBind3`,moduleName:v};static pipeBind4={name:`ɵɵpipeBind4`,moduleName:v};static pipeBindV={name:`ɵɵpipeBindV`,moduleName:v};static domProperty={name:`ɵɵdomProperty`,moduleName:v};static ariaProperty={name:`ɵɵariaProperty`,moduleName:v};static property={name:`ɵɵproperty`,moduleName:v};static control={name:`ɵɵcontrol`,moduleName:v};static controlCreate={name:`ɵɵcontrolCreate`,moduleName:v};static animationEnterListener={name:`ɵɵanimateEnterListener`,moduleName:v};static animationLeaveListener={name:`ɵɵanimateLeaveListener`,moduleName:v};static animationEnter={name:`ɵɵanimateEnter`,moduleName:v};static animationLeave={name:`ɵɵanimateLeave`,moduleName:v};static i18n={name:`ɵɵi18n`,moduleName:v};static i18nAttributes={name:`ɵɵi18nAttributes`,moduleName:v};static i18nExp={name:`ɵɵi18nExp`,moduleName:v};static i18nStart={name:`ɵɵi18nStart`,moduleName:v};static i18nEnd={name:`ɵɵi18nEnd`,moduleName:v};static i18nApply={name:`ɵɵi18nApply`,moduleName:v};static i18nPostprocess={name:`ɵɵi18nPostprocess`,moduleName:v};static pipe={name:`ɵɵpipe`,moduleName:v};static projection={name:`ɵɵprojection`,moduleName:v};static projectionDef={name:`ɵɵprojectionDef`,moduleName:v};static reference={name:`ɵɵreference`,moduleName:v};static inject={name:`ɵɵinject`,moduleName:v};static injectAttribute={name:`ɵɵinjectAttribute`,moduleName:v};static directiveInject={name:`ɵɵdirectiveInject`,moduleName:v};static invalidFactory={name:`ɵɵinvalidFactory`,moduleName:v};static invalidFactoryDep={name:`ɵɵinvalidFactoryDep`,moduleName:v};static templateRefExtractor={name:`ɵɵtemplateRefExtractor`,moduleName:v};static forwardRef={name:`forwardRef`,moduleName:v};static resolveForwardRef={name:`resolveForwardRef`,moduleName:v};static replaceMetadata={name:`ɵɵreplaceMetadata`,moduleName:v};static getReplaceMetadataURL={name:`ɵɵgetReplaceMetadataURL`,moduleName:v};static ɵɵdefineInjectable={name:`ɵɵdefineInjectable`,moduleName:v};static declareInjectable={name:`ɵɵngDeclareInjectable`,moduleName:v};static InjectableDeclaration={name:`ɵɵInjectableDeclaration`,moduleName:v};static defineService={name:`ɵɵdefineService`,moduleName:v};static declareService={name:`ɵɵngDeclareService`,moduleName:v};static resolveWindow={name:`ɵɵresolveWindow`,moduleName:v};static resolveDocument={name:`ɵɵresolveDocument`,moduleName:v};static resolveBody={name:`ɵɵresolveBody`,moduleName:v};static getComponentDepsFactory={name:`ɵɵgetComponentDepsFactory`,moduleName:v};static defineComponent={name:`ɵɵdefineComponent`,moduleName:v};static declareComponent={name:`ɵɵngDeclareComponent`,moduleName:v};static setComponentScope={name:`ɵɵsetComponentScope`,moduleName:v};static ChangeDetectionStrategy={name:`ChangeDetectionStrategy`,moduleName:v};static ViewEncapsulation={name:`ViewEncapsulation`,moduleName:v};static ComponentDeclaration={name:`ɵɵComponentDeclaration`,moduleName:v};static FactoryDeclaration={name:`ɵɵFactoryDeclaration`,moduleName:v};static declareFactory={name:`ɵɵngDeclareFactory`,moduleName:v};static FactoryTarget={name:`ɵɵFactoryTarget`,moduleName:v};static defineDirective={name:`ɵɵdefineDirective`,moduleName:v};static declareDirective={name:`ɵɵngDeclareDirective`,moduleName:v};static DirectiveDeclaration={name:`ɵɵDirectiveDeclaration`,moduleName:v};static InjectorDef={name:`ɵɵInjectorDef`,moduleName:v};static InjectorDeclaration={name:`ɵɵInjectorDeclaration`,moduleName:v};static defineInjector={name:`ɵɵdefineInjector`,moduleName:v};static declareInjector={name:`ɵɵngDeclareInjector`,moduleName:v};static NgModuleDeclaration={name:`ɵɵNgModuleDeclaration`,moduleName:v};static ModuleWithProviders={name:`ModuleWithProviders`,moduleName:v};static defineNgModule={name:`ɵɵdefineNgModule`,moduleName:v};static declareNgModule={name:`ɵɵngDeclareNgModule`,moduleName:v};static setNgModuleScope={name:`ɵɵsetNgModuleScope`,moduleName:v};static registerNgModuleType={name:`ɵɵregisterNgModuleType`,moduleName:v};static PipeDeclaration={name:`ɵɵPipeDeclaration`,moduleName:v};static definePipe={name:`ɵɵdefinePipe`,moduleName:v};static declarePipe={name:`ɵɵngDeclarePipe`,moduleName:v};static declareClassMetadata={name:`ɵɵngDeclareClassMetadata`,moduleName:v};static declareClassMetadataAsync={name:`ɵɵngDeclareClassMetadataAsync`,moduleName:v};static setClassMetadata={name:`ɵsetClassMetadata`,moduleName:v};static setClassMetadataAsync={name:`ɵsetClassMetadataAsync`,moduleName:v};static setClassDebugInfo={name:`ɵsetClassDebugInfo`,moduleName:v};static queryRefresh={name:`ɵɵqueryRefresh`,moduleName:v};static viewQuery={name:`ɵɵviewQuery`,moduleName:v};static loadQuery={name:`ɵɵloadQuery`,moduleName:v};static contentQuery={name:`ɵɵcontentQuery`,moduleName:v};static viewQuerySignal={name:`ɵɵviewQuerySignal`,moduleName:v};static contentQuerySignal={name:`ɵɵcontentQuerySignal`,moduleName:v};static queryAdvance={name:`ɵɵqueryAdvance`,moduleName:v};static twoWayProperty={name:`ɵɵtwoWayProperty`,moduleName:v};static twoWayBindingSet={name:`ɵɵtwoWayBindingSet`,moduleName:v};static twoWayListener={name:`ɵɵtwoWayListener`,moduleName:v};static declareLet={name:`ɵɵdeclareLet`,moduleName:v};static storeLet={name:`ɵɵstoreLet`,moduleName:v};static readContextLet={name:`ɵɵreadContextLet`,moduleName:v};static arrowFunction={name:`ɵɵarrowFunction`,moduleName:v};static attachSourceLocations={name:`ɵɵattachSourceLocations`,moduleName:v};static NgOnChangesFeature={name:`ɵɵNgOnChangesFeature`,moduleName:v};static ControlFeature={name:`ɵɵControlFeature`,moduleName:v};static InheritDefinitionFeature={name:`ɵɵInheritDefinitionFeature`,moduleName:v};static ProvidersFeature={name:`ɵɵProvidersFeature`,moduleName:v};static HostDirectivesFeature={name:`ɵɵHostDirectivesFeature`,moduleName:v};static ExternalStylesFeature={name:`ɵɵExternalStylesFeature`,moduleName:v};static listener={name:`ɵɵlistener`,moduleName:v};static getInheritedFactory={name:`ɵɵgetInheritedFactory`,moduleName:v};static sanitizeHtml={name:`ɵɵsanitizeHtml`,moduleName:v};static sanitizeStyle={name:`ɵɵsanitizeStyle`,moduleName:v};static validateAttribute={name:`ɵɵvalidateAttribute`,moduleName:v};static sanitizeResourceUrl={name:`ɵɵsanitizeResourceUrl`,moduleName:v};static sanitizeScript={name:`ɵɵsanitizeScript`,moduleName:v};static sanitizeUrl={name:`ɵɵsanitizeUrl`,moduleName:v};static sanitizeUrlOrResourceUrl={name:`ɵɵsanitizeUrlOrResourceUrl`,moduleName:v};static trustConstantHtml={name:`ɵɵtrustConstantHtml`,moduleName:v};static trustConstantResourceUrl={name:`ɵɵtrustConstantResourceUrl`,moduleName:v};static inputDecorator={name:`Input`,moduleName:v};static outputDecorator={name:`Output`,moduleName:v};static viewChildDecorator={name:`ViewChild`,moduleName:v};static viewChildrenDecorator={name:`ViewChildren`,moduleName:v};static contentChildDecorator={name:`ContentChild`,moduleName:v};static contentChildrenDecorator={name:`ContentChildren`,moduleName:v};static InputSignalBrandWriteType={name:`ɵINPUT_SIGNAL_BRAND_WRITE_TYPE`,moduleName:v};static UnwrapDirectiveSignalInputs={name:`ɵUnwrapDirectiveSignalInputs`,moduleName:v};static unwrapWritableSignal={name:`ɵunwrapWritableSignal`,moduleName:v};static assertType={name:`ɵassertType`,moduleName:v}}return e})();_.And,_.Bigger,_.BiggerEquals,_.BitwiseOr,_.BitwiseAnd,_.Divide,_.Assign,_.Equals,_.Identical,_.Lower,_.LowerEquals,_.Minus,_.Modulo,_.Exponentiation,_.Multiply,_.NotEquals,_.NotIdentical,_.NullishCoalesce,_.Or,_.Plus,_.In,_.InstanceOf,_.AdditionAssignment,_.SubtractionAssignment,_.MultiplicationAssignment,_.DivisionAssignment,_.RemainderAssignment,_.ExponentiationAssignment,_.AndAssignment,_.OrAssignment,_.NullishCoalesceAssignment;var wt=class{span;sourceSpan;constructor(e,t){this.span=e,this.sourceSpan=t}toString(){return`AST`}},Tt=class extends wt{receiver;args;argumentSpan;constructor(e,t,n,r,i){super(e,t),this.receiver=n,this.args=r,this.argumentSpan=i}visit(e,t=null){return e.visitCall(this,t)}},Et=(function(e){return e[e.Property=0]=`Property`,e[e.Attribute=1]=`Attribute`,e[e.Class=2]=`Class`,e[e.Style=3]=`Style`,e[e.LegacyAnimation=4]=`LegacyAnimation`,e[e.TwoWay=5]=`TwoWay`,e[e.Animation=6]=`Animation`,e})(Et||{}),Dt=`(:(where|is)\\()?`,Ot=`-shadowcsshost`,kt=`-shadowcsscontext`,At=`[^)(]*`,jt=String.raw`(?:\(${At}\)|${At})+?`,Mt=String.raw`(?:\(${jt}\)|${At})+?`,Nt=String.raw`(?:\((${Mt})\))`;String.raw`(:nth-[-\w]+)`+Nt,Ot+Nt+``,`${Dt}`,kt+Nt+``;var Pt=(function(e){return e[e.ListEnd=0]=`ListEnd`,e[e.Statement=1]=`Statement`,e[e.Variable=2]=`Variable`,e[e.ElementStart=3]=`ElementStart`,e[e.Element=4]=`Element`,e[e.ForeignComponent=5]=`ForeignComponent`,e[e.Template=6]=`Template`,e[e.ElementEnd=7]=`ElementEnd`,e[e.ContainerStart=8]=`ContainerStart`,e[e.Container=9]=`Container`,e[e.ContainerEnd=10]=`ContainerEnd`,e[e.DisableBindings=11]=`DisableBindings`,e[e.ConditionalCreate=12]=`ConditionalCreate`,e[e.ConditionalBranchCreate=13]=`ConditionalBranchCreate`,e[e.Conditional=14]=`Conditional`,e[e.EnableBindings=15]=`EnableBindings`,e[e.Text=16]=`Text`,e[e.Listener=17]=`Listener`,e[e.InterpolateText=18]=`InterpolateText`,e[e.Binding=19]=`Binding`,e[e.Property=20]=`Property`,e[e.StyleProp=21]=`StyleProp`,e[e.ClassProp=22]=`ClassProp`,e[e.StyleMap=23]=`StyleMap`,e[e.ClassMap=24]=`ClassMap`,e[e.Advance=25]=`Advance`,e[e.Pipe=26]=`Pipe`,e[e.Attribute=27]=`Attribute`,e[e.ExtractedAttribute=28]=`ExtractedAttribute`,e[e.Defer=29]=`Defer`,e[e.DeferOn=30]=`DeferOn`,e[e.DeferWhen=31]=`DeferWhen`,e[e.I18nMessage=32]=`I18nMessage`,e[e.DomProperty=33]=`DomProperty`,e[e.Namespace=34]=`Namespace`,e[e.ProjectionDef=35]=`ProjectionDef`,e[e.EnableIncrementalHydrationRuntime=36]=`EnableIncrementalHydrationRuntime`,e[e.Projection=37]=`Projection`,e[e.Content=38]=`Content`,e[e.RepeaterCreate=39]=`RepeaterCreate`,e[e.Repeater=40]=`Repeater`,e[e.TwoWayProperty=41]=`TwoWayProperty`,e[e.TwoWayListener=42]=`TwoWayListener`,e[e.DeclareLet=43]=`DeclareLet`,e[e.StoreLet=44]=`StoreLet`,e[e.I18nStart=45]=`I18nStart`,e[e.I18n=46]=`I18n`,e[e.I18nEnd=47]=`I18nEnd`,e[e.I18nExpression=48]=`I18nExpression`,e[e.I18nApply=49]=`I18nApply`,e[e.IcuStart=50]=`IcuStart`,e[e.IcuEnd=51]=`IcuEnd`,e[e.IcuPlaceholder=52]=`IcuPlaceholder`,e[e.I18nContext=53]=`I18nContext`,e[e.I18nAttributes=54]=`I18nAttributes`,e[e.SourceLocation=55]=`SourceLocation`,e[e.Animation=56]=`Animation`,e[e.AnimationString=57]=`AnimationString`,e[e.AnimationBinding=58]=`AnimationBinding`,e[e.AnimationListener=59]=`AnimationListener`,e[e.Control=60]=`Control`,e[e.ControlCreate=61]=`ControlCreate`,e})(Pt||{}),Ft=(function(e){return e[e.LexicalRead=0]=`LexicalRead`,e[e.Context=1]=`Context`,e[e.TrackContext=2]=`TrackContext`,e[e.ReadVariable=3]=`ReadVariable`,e[e.NextContext=4]=`NextContext`,e[e.Reference=5]=`Reference`,e[e.StoreLet=6]=`StoreLet`,e[e.ContextLetReference=7]=`ContextLetReference`,e[e.GetCurrentView=8]=`GetCurrentView`,e[e.RestoreView=9]=`RestoreView`,e[e.ResetView=10]=`ResetView`,e[e.PureFunctionExpr=11]=`PureFunctionExpr`,e[e.PureFunctionParameterExpr=12]=`PureFunctionParameterExpr`,e[e.PipeBinding=13]=`PipeBinding`,e[e.PipeBindingVariadic=14]=`PipeBindingVariadic`,e[e.SafePropertyRead=15]=`SafePropertyRead`,e[e.SafeKeyedRead=16]=`SafeKeyedRead`,e[e.SafeNavigationMigration=17]=`SafeNavigationMigration`,e[e.SafeTernaryExpr=18]=`SafeTernaryExpr`,e[e.EmptyExpr=19]=`EmptyExpr`,e[e.AssignTemporaryExpr=20]=`AssignTemporaryExpr`,e[e.ReadTemporaryExpr=21]=`ReadTemporaryExpr`,e[e.SlotLiteralExpr=22]=`SlotLiteralExpr`,e[e.ConditionalCase=23]=`ConditionalCase`,e[e.ConstCollected=24]=`ConstCollected`,e[e.TwoWayBindingSet=25]=`TwoWayBindingSet`,e[e.ForeignContent=26]=`ForeignContent`,e[e.ArrowFunction=27]=`ArrowFunction`,e})(Ft||{}),It=(function(e){return e[e.None=0]=`None`,e[e.AlwaysInline=1]=`AlwaysInline`,e})(It||{}),Lt=(function(e){return e[e.Context=0]=`Context`,e[e.Identifier=1]=`Identifier`,e[e.SavedView=2]=`SavedView`,e[e.Alias=3]=`Alias`,e})(Lt||{}),Rt=(function(e){return e[e.Attribute=0]=`Attribute`,e[e.ClassName=1]=`ClassName`,e[e.StyleProperty=2]=`StyleProperty`,e[e.Property=3]=`Property`,e[e.Template=4]=`Template`,e[e.I18n=5]=`I18n`,e[e.LegacyAnimation=6]=`LegacyAnimation`,e[e.TwoWayProperty=7]=`TwoWayProperty`,e[e.Animation=8]=`Animation`,e})(Rt||{}),zt=(function(e){return e[e.Creation=0]=`Creation`,e[e.Postproccessing=1]=`Postproccessing`,e})(zt||{}),Bt=(function(e){return e[e.I18nText=0]=`I18nText`,e[e.I18nAttribute=1]=`I18nAttribute`,e})(Bt||{}),Vt=(function(e){return e[e.None=0]=`None`,e[e.ElementTag=1]=`ElementTag`,e[e.TemplateTag=2]=`TemplateTag`,e[e.OpenTag=4]=`OpenTag`,e[e.CloseTag=8]=`CloseTag`,e[e.ExpressionIndex=16]=`ExpressionIndex`,e})(Vt||{}),Ht=(function(e){return e[e.HTML=0]=`HTML`,e[e.SVG=1]=`SVG`,e[e.Math=2]=`Math`,e})(Ht||{}),Ut=(function(e){return e[e.Idle=0]=`Idle`,e[e.Immediate=1]=`Immediate`,e[e.Timer=2]=`Timer`,e[e.Hover=3]=`Hover`,e[e.Interaction=4]=`Interaction`,e[e.Viewport=5]=`Viewport`,e[e.Never=6]=`Never`,e})(Ut||{}),Wt=(function(e){return e[e.RootI18n=0]=`RootI18n`,e[e.Icu=1]=`Icu`,e[e.Attr=2]=`Attr`,e})(Wt||{}),Gt=(function(e){return e[e.NgTemplate=0]=`NgTemplate`,e[e.Structural=1]=`Structural`,e[e.Block=2]=`Block`,e})(Gt||{}),Kt=(function(e){return e[e.None=0]=`None`,e[e.InChildOperation=1]=`InChildOperation`,e[e.InArrowFunctionOperation=2]=`InArrowFunctionOperation`,e[e.InSafeNavigationMigration=4]=`InSafeNavigationMigration`,e})(Kt||{});Pt.Element,Pt.ElementStart,Pt.Container,Pt.ContainerStart,Pt.Template,Pt.RepeaterCreate,Pt.ConditionalCreate,Pt.ConditionalBranchCreate;var b=(function(e){return e[e.Tmpl=0]=`Tmpl`,e[e.Host=1]=`Host`,e[e.Both=2]=`Both`,e})(b||{}),qt=(function(e){return e[e.Full=0]=`Full`,e[e.DomOnly=1]=`DomOnly`,e})(qt||{});y.ariaProperty,y.ariaProperty,y.attribute,y.attribute,y.classProp,y.classProp,y.element,y.element,y.elementContainer,y.elementContainer,y.elementContainerEnd,y.elementContainerEnd,y.elementContainerStart,y.elementContainerStart,y.elementEnd,y.elementEnd,y.elementStart,y.elementStart,y.domProperty,y.domProperty,y.i18nExp,y.i18nExp,y.listener,y.listener,y.listener,y.listener,y.property,y.property,y.styleProp,y.styleProp,y.syntheticHostListener,y.syntheticHostListener,y.syntheticHostProperty,y.syntheticHostProperty,y.templateCreate,y.templateCreate,y.twoWayProperty,y.twoWayProperty,y.twoWayListener,y.twoWayListener,y.declareLet,y.declareLet,y.conditionalCreate,y.conditionalBranchCreate,y.conditionalBranchCreate,y.conditionalBranchCreate,y.domElement,y.domElement,y.domElementStart,y.domElementStart,y.domElementEnd,y.domElementEnd,y.domElementContainer,y.domElementContainer,y.domElementContainerStart,y.domElementContainerStart,y.domElementContainerEnd,y.domElementContainerEnd,y.domListener,y.domListener,y.domTemplate,y.domTemplate,y.animationEnter,y.animationEnter,y.animationLeave,y.animationLeave,y.animationEnterListener,y.animationEnterListener,y.animationLeaveListener,y.animationLeaveListener,_.And,_.Bigger,_.BiggerEquals,_.BitwiseOr,_.BitwiseAnd,_.Divide,_.Assign,_.Equals,_.Identical,_.Lower,_.LowerEquals,_.Minus,_.Modulo,_.Exponentiation,_.Multiply,_.NotEquals,_.NotIdentical,_.NullishCoalesce,_.Or,_.Plus,_.In,_.InstanceOf,_.AdditionAssignment,_.SubtractionAssignment,_.MultiplicationAssignment,_.DivisionAssignment,_.RemainderAssignment,_.ExponentiationAssignment,_.AndAssignment,_.OrAssignment,_.NullishCoalesceAssignment,Pt.Property,Pt.Property,Pt.Property,Pt.Attribute,Pt.Attribute,Pt.Property,Pt.TwoWayProperty,Pt.Container,Pt.ContainerStart,Pt.ContainerEnd,Pt.Element,Pt.ElementStart,Pt.ElementEnd,Pt.Template,Pt.ElementEnd,Pt.ElementStart,Pt.Element,Pt.ContainerEnd,Pt.ContainerStart,Pt.Container,Pt.I18nEnd,Pt.I18nStart,Pt.I18n,Pt.Pipe;var Jt=` \f +\r \v ᠎ - \u2028\u2029   `;`${Jt}`,`${Jt}`;var Yt=(function(e){return e[e.Character=0]=`Character`,e[e.Identifier=1]=`Identifier`,e[e.PrivateIdentifier=2]=`PrivateIdentifier`,e[e.Keyword=3]=`Keyword`,e[e.String=4]=`String`,e[e.Operator=5]=`Operator`,e[e.Number=6]=`Number`,e[e.RegExpBody=7]=`RegExpBody`,e[e.RegExpFlags=8]=`RegExpFlags`,e[e.Error=9]=`Error`,e})(Yt||{}),Xt=(function(e){return e[e.Plain=0]=`Plain`,e[e.TemplateLiteralPart=1]=`TemplateLiteralPart`,e[e.TemplateLiteralEnd=2]=`TemplateLiteralEnd`,e})(Xt||{});Yt.Character,Pt.StyleMap,Pt.ClassMap,Pt.StyleProp,Pt.ClassProp,Pt.Attribute,Pt.Property,Pt.Attribute,Pt.Control,Pt.DomProperty,Pt.DomProperty,Pt.Attribute,Pt.StyleMap,Pt.ClassMap,Pt.StyleProp,Pt.ClassProp,Pt.Listener,Pt.TwoWayListener,Pt.AnimationListener,Pt.StyleMap,Pt.ClassMap,Pt.StyleProp,Pt.ClassProp,Pt.Property,Pt.TwoWayProperty,Pt.DomProperty,Pt.Attribute,Pt.Animation,Pt.Control,Ut.Idle,y.deferOnIdle,y.deferPrefetchOnIdle,y.deferHydrateOnIdle,Ut.Immediate,y.deferOnImmediate,y.deferPrefetchOnImmediate,y.deferHydrateOnImmediate,Ut.Timer,y.deferOnTimer,y.deferPrefetchOnTimer,y.deferHydrateOnTimer,Ut.Hover,y.deferOnHover,y.deferPrefetchOnHover,y.deferHydrateOnHover,Ut.Interaction,y.deferOnInteraction,y.deferPrefetchOnInteraction,y.deferHydrateOnInteraction,Ut.Viewport,y.deferOnViewport,y.deferPrefetchOnViewport,y.deferHydrateOnViewport,Ut.Never,y.deferHydrateNever,y.deferHydrateNever,y.deferHydrateNever,y.pipeBind1,y.pipeBind2,y.pipeBind3,y.pipeBind4,y.textInterpolate,y.textInterpolate1,y.textInterpolate2,y.textInterpolate3,y.textInterpolate4,y.textInterpolate5,y.textInterpolate6,y.textInterpolate7,y.textInterpolate8,y.textInterpolateV,y.interpolate,y.interpolate1,y.interpolate2,y.interpolate3,y.interpolate4,y.interpolate5,y.interpolate6,y.interpolate7,y.interpolate8,y.interpolateV,y.pureFunction0,y.pureFunction1,y.pureFunction2,y.pureFunction3,y.pureFunction4,y.pureFunction5,y.pureFunction6,y.pureFunction7,y.pureFunction8,y.pureFunctionV,y.resolveWindow,y.resolveDocument,y.resolveBody,rt.HTML,y.sanitizeHtml,rt.RESOURCE_URL,y.sanitizeResourceUrl,rt.SCRIPT,y.sanitizeScript,rt.STYLE,y.sanitizeStyle,rt.URL,y.sanitizeUrl,rt.ATTRIBUTE_NO_BINDING,y.validateAttribute,rt.HTML,y.trustConstantHtml,rt.RESOURCE_URL,y.trustConstantResourceUrl;var Zt=(function(e){return e[e.None=0]=`None`,e[e.ViewContextRead=1]=`ViewContextRead`,e[e.ViewContextWrite=2]=`ViewContextWrite`,e[e.SideEffectful=4]=`SideEffectful`,e})(Zt||{});b.Tmpl,b.Tmpl,b.Both,b.Host,b.Tmpl,b.Tmpl,b.Tmpl,b.Both,b.Both,b.Both,b.Tmpl,b.Both,b.Both,b.Tmpl,b.Both,b.Tmpl,b.Both,b.Both,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Both,b.Both,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Both,b.Both,b.Both,b.Tmpl,b.Tmpl,b.Both,b.Tmpl,b.Tmpl,b.Tmpl,b.Both,b.Both,b.Tmpl,b.Both,b.Both,b.Both,b.Both,b.Both,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Both,b.Tmpl,b.Both,b.Tmpl,b.Both,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Tmpl,b.Both,b.Both,b.Both,Et.Property,Rt.Property,Et.TwoWay,Rt.TwoWayProperty,Et.Attribute,Rt.Attribute,Et.Class,Rt.ClassName,Et.Style,Rt.StyleProperty,Et.LegacyAnimation,Rt.LegacyAnimation,Et.Animation,Rt.Animation;var Qt=`%COMP%`;`${Qt}`,`${Qt}`,class e{static SINGLETON=new e;static veWillInferAnyFor(t){let n=e.SINGLETON;return t instanceof Tt?t.visit(n):t.receiver.visit(n)}visitUnary(e){return e.expr.visit(this)}visitBinary(e){return e.left.visit(this)||e.right.visit(this)}visitChain(){return!1}visitConditional(e){return e.condition.visit(this)||e.trueExp.visit(this)||e.falseExp.visit(this)}visitCall(){return!0}visitSafeCall(){return!1}visitImplicitReceiver(){return!1}visitThisReceiver(){return!1}visitInterpolation(e){return e.expressions.some(e=>e.visit(this))}visitKeyedRead(){return!1}visitLiteralArray(){return!0}visitLiteralMap(){return!0}visitLiteralPrimitive(){return!1}visitPipe(){return!0}visitPrefixNot(e){return e.expression.visit(this)}visitTypeofExpression(e){return e.expression.visit(this)}visitVoidExpression(e){return e.expression.visit(this)}visitNonNullAssert(e){return e.expression.visit(this)}visitPropertyRead(){return!1}visitSafePropertyRead(){return!1}visitSafeKeyedRead(){return!1}visitTemplateLiteral(){return!1}visitTemplateLiteralElement(){return!1}visitTaggedTemplateLiteral(){return!1}visitParenthesizedExpression(e){return e.expression.visit(this)}visitRegularExpressionLiteral(){return!1}visitSpreadElement(e){return e.expression.visit(this)}visitArrowFunction(e,t){return!1}};var $t=null,en=!1,tn=1,nn=null,rn=Symbol(`SIGNAL`);function an(e){let t=$t;return $t=e,t}function on(){return $t}var sn={version:0,lastCleanEpoch:0,dirty:!1,producers:void 0,producersTail:void 0,consumers:void 0,consumersTail:void 0,recomputing:!1,consumerAllowSignalWrites:!1,consumerIsAlwaysLive:!1,kind:`unknown`,producerMustRecompute:()=>!1,producerRecomputeValue:()=>{},consumerMarkedDirty:()=>{},consumerOnSignalRead:()=>{}};function cn(e){if(en)throw Error(``);if($t===null)return;$t.consumerOnSignalRead(e);let t=$t.producersTail;if(t!==void 0&&t.producer===e)return;let n,r=$t.recomputing;if(r&&(n=t===void 0?$t.producers:t.nextProducer,n!==void 0&&n.producer===e)){$t.producersTail=n,n.lastReadVersion=e.version,n.knownValidAtEpoch=tn;return}let i=e.consumersTail;if(i!==void 0&&i.consumer===$t&&(!r||i.knownValidAtEpoch===tn))return;let a=Cn($t),o={producer:e,consumer:$t,nextProducer:n,prevConsumer:void 0,knownValidAtEpoch:tn,lastReadVersion:e.version,nextConsumer:void 0};$t.producersTail=o,t===void 0?$t.producers=o:t.nextProducer=o,a&&xn(e,o)}function ln(){tn++}function un(e){if((!Cn(e)||e.dirty)&&(e.dirty||e.lastCleanEpoch!==tn)){if(!e.producerMustRecompute(e)&&!yn(e)){mn(e);return}e.producerRecomputeValue(e),mn(e)}}function dn(e){if(e.consumers===void 0)return;let t=en;en=!0;try{for(let t=e.consumers;t!==void 0;t=t.nextConsumer){let e=t.consumer;e.dirty||pn(e)}}finally{en=t}}function fn(){return $t?.consumerAllowSignalWrites!==!1}function pn(e){e.dirty=!0,dn(e),e.consumerMarkedDirty?.(e)}function mn(e){e.dirty=!1,e.lastCleanEpoch=tn}function hn(e){return e&&gn(e),an(e)}function gn(e){if(e.producersTail?.knownValidAtEpoch===tn){let t=e.producers;for(;t!==void 0;)t.knownValidAtEpoch=null,t=t.nextProducer}e.producersTail=void 0,e.recomputing=!0}function _n(e,t){an(t),e&&vn(e)}function vn(e){e.recomputing=!1;let t=e.producersTail,n=t===void 0?e.producers:t.nextProducer;if(n!==void 0){if(Cn(e))do n=Sn(n);while(n!==void 0);t===void 0?e.producers=void 0:t.nextProducer=void 0}}function yn(e){for(let t=e.producers;t!==void 0;t=t.nextProducer){let e=t.producer,n=t.lastReadVersion;if(n!==e.version||(un(e),n!==e.version))return!0}return!1}function bn(e){if(Cn(e)){let t=e.producers;for(;t!==void 0;)t=Sn(t)}e.producers=void 0,e.producersTail=void 0,e.consumers=void 0,e.consumersTail=void 0}function xn(e,t){let n=e.consumersTail,r=Cn(e);if(n===void 0?(t.nextConsumer=void 0,e.consumers=t):(t.nextConsumer=n.nextConsumer,n.nextConsumer=t),t.prevConsumer=n,e.consumersTail=t,!r)for(let t=e.producers;t!==void 0;t=t.nextProducer)xn(t.producer,t)}function Sn(e){let t=e.producer,n=e.nextProducer,r=e.nextConsumer,i=e.prevConsumer;if(e.nextConsumer=void 0,e.prevConsumer=void 0,r===void 0?t.consumersTail=i:r.prevConsumer=i,i!==void 0)i.nextConsumer=r;else if(t.consumers=r,!Cn(t)){let e=t.producers;for(;e!==void 0;)e=Sn(e)}return n}function Cn(e){return e.consumerIsAlwaysLive||e.consumers!==void 0}function wn(e){nn?.(e)}function Tn(e,t){return Object.is(e,t)}function En(e,t){let n=Object.create(uee);n.computation=e,t!==void 0&&(n.equal=t);let r=()=>{if(un(n),cn(n),n.value===kn)throw n.error;return n.value};return r[rn]=n,wn(n),r}var Dn=Symbol(`UNSET`),On=Symbol(`COMPUTING`),kn=Symbol(`ERRORED`),uee={...sn,value:Dn,dirty:!0,error:null,equal:Tn,kind:`computed`,producerMustRecompute(e){return e.value===Dn||e.value===On},producerRecomputeValue(e){if(e.value===On)throw Error(``);let t=e.value;e.value=On;let n=hn(e),r,i=!1;try{r=e.computation(),an(null),i=t!==Dn&&t!==kn&&r!==kn&&e.equal(t,r)}catch(t){r=kn,e.error=t}finally{_n(e,n)}if(i){e.value=t;return}e.value=r,e.version++}};function dee(){throw Error()}var An=dee;function jn(e){An(e)}function fee(e){An=e}var pee=null;function mee(e,t){let n=Object.create(Pn);n.value=e,t!==void 0&&(n.equal=t);let r=()=>hee(n);return r[rn]=n,wn(n),[r,e=>Mn(n,e),e=>Nn(n,e)]}function hee(e){return cn(e),e.value}function Mn(e,t){fn()||jn(e),e.equal(e.value,t)||(e.value=t,Fn(e))}function Nn(e,t){fn()||jn(e),Mn(e,t(e.value))}var Pn={...sn,equal:Tn,value:void 0,kind:`signal`};function Fn(e){e.version++,ln(),dn(e),pee?.(e)}var In={...sn,consumerIsAlwaysLive:!0,consumerAllowSignalWrites:!0,dirty:!0,kind:`effect`};function Ln(e){if(e.dirty=!1,e.version>0&&!yn(e))return;e.version++;let t=hn(e);try{e.cleanup(),e.fn()}finally{_n(e,t)}}var Rn=void 0;function zn(){return Rn}function Bn(e){let t=Rn;return Rn=e,t}var Vn=Symbol(`NotFound`);function Hn(e){return e===Vn||e?.name===`ɵNotFound`}function Un(e,t,n){let r=Object.create(Kn);r.source=e,r.computation=t,n!=null&&(r.equal=n);let i=()=>{if(un(r),cn(r),r.value===kn)throw r.error;return r.value};return i[rn]=r,wn(r),i}function Wn(e,t){un(e),Mn(e,t),mn(e)}function Gn(e,t){if(un(e),e.value===kn)throw e.error;Nn(e,t),mn(e)}var Kn={...sn,value:Dn,dirty:!0,error:null,equal:Tn,kind:`linkedSignal`,producerMustRecompute(e){return e.value===Dn||e.value===On},producerRecomputeValue(e){if(e.value===On)throw Error(``);let t=e.value;e.value=On;let n=hn(e),r,i=!1;try{let n=e.source(),a=t!==Dn&&t!==kn,o=a?{source:e.sourceValue,value:t}:void 0;r=e.computation(n,o),e.sourceValue=n,an(null),i=a&&r!==kn&&e.equal(t,r)}catch(t){r=kn,e.error=t}finally{_n(e,n)}if(i){e.value=t;return}e.value=r,e.version++}};function qn(e){let t=an(null);try{return e()}finally{an(t)}}var Jn=function(e,t){return Jn=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},Jn(e,t)};function Yn(e,t){if(typeof t!=`function`&&t!==null)throw TypeError(`Class extends value `+String(t)+` is not a constructor or null`);Jn(e,t);function n(){this.constructor=e}e.prototype=t===null?Object.create(t):(n.prototype=t.prototype,new n)}function Xn(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||=Promise)(function(n,a){function o(e){try{c(r.next(e))}catch(e){a(e)}}function s(e){try{c(r.throw(e))}catch(e){a(e)}}function c(e){e.done?n(e.value):i(e.value).then(o,s)}c((r=r.apply(e,t||[])).next())})}function Zn(e,t){var n={label:0,sent:function(){if(a[0]&1)throw a[1];return a[1]},trys:[],ops:[]},r,i,a,o=Object.create((typeof Iterator==`function`?Iterator:Object).prototype);return o.next=s(0),o.throw=s(1),o.return=s(2),typeof Symbol==`function`&&(o[Symbol.iterator]=function(){return this}),o;function s(e){return function(t){return c([e,t])}}function c(s){if(r)throw TypeError(`Generator is already executing.`);for(;o&&(o=0,s[0]&&(n=0)),n;)try{if(r=1,i&&(a=s[0]&2?i.return:s[0]?i.throw||((a=i.return)&&a.call(i),0):i.next)&&!(a=a.call(i,s[1])).done)return a;switch(i=0,a&&(s=[s[0]&2,a.value]),s[0]){case 0:case 1:a=s;break;case 4:return n.label++,{value:s[1],done:!1};case 5:n.label++,i=s[1],s=[0];continue;case 7:s=n.ops.pop(),n.trys.pop();continue;default:if(a=n.trys,!(a=a.length>0&&a[a.length-1])&&(s[0]===6||s[0]===2)){n=0;continue}if(s[0]===3&&(!a||s[1]>a[0]&&s[1]=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw TypeError(t?`Object is not iterable.`:`Symbol.iterator is not defined.`)}function $n(e,t){var n=typeof Symbol==`function`&&e[Symbol.iterator];if(!n)return e;var r=n.call(e),i,a=[],o;try{for(;(t===void 0||t-->0)&&!(i=r.next()).done;)a.push(i.value)}catch(e){o={error:e}}finally{try{i&&!i.done&&(n=r.return)&&n.call(r)}finally{if(o)throw o.error}}return a}function er(e,t,n){if(n||arguments.length===2)for(var r=0,i=t.length,a;r1||c(e,t)})},t&&(i[e]=t(i[e])))}function c(e,t){try{l(r[e](t))}catch(e){f(a[0][3],e)}}function l(e){e.value instanceof tr?Promise.resolve(e.value.v).then(u,d):f(a[0][2],e)}function u(e){c(`next`,e)}function d(e){c(`throw`,e)}function f(e,t){e(t),a.shift(),a.length&&c(a[0][0],a[0][1])}}function rr(e){if(!Symbol.asyncIterator)throw TypeError(`Symbol.asyncIterator is not defined.`);var t=e[Symbol.asyncIterator],n;return t?t.call(e):(e=typeof Qn==`function`?Qn(e):e[Symbol.iterator](),n={},r(`next`),r(`throw`),r(`return`),n[Symbol.asyncIterator]=function(){return this},n);function r(t){n[t]=e[t]&&function(n){return new Promise(function(r,a){n=e[t](n),i(r,a,n.done,n.value)})}}function i(e,t,n,r){Promise.resolve(r).then(function(t){e({value:t,done:n})},t)}}function ir(e){return typeof e==`function`}function ar(e){var t=e(function(e){Error.call(e),e.stack=Error().stack});return t.prototype=Object.create(Error.prototype),t.prototype.constructor=t,t}var or=ar(function(e){return function(t){e(this),this.message=t?t.length+` errors occurred during unsubscription: +`+t.map(function(e,t){return t+1+`) `+e.toString()}).join(` + `):``,this.name=`UnsubscriptionError`,this.errors=t}});function sr(e,t){if(e){var n=e.indexOf(t);0<=n&&e.splice(n,1)}}var cr=function(){function e(e){this.initialTeardown=e,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var e,t,n,r,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a){if(this._parentage=null,Array.isArray(a))try{for(var o=Qn(a),s=o.next();!s.done;s=o.next())s.value.remove(this)}catch(t){e={error:t}}finally{try{s&&!s.done&&(t=o.return)&&t.call(o)}finally{if(e)throw e.error}}else a.remove(this)}var c=this.initialTeardown;if(ir(c))try{c()}catch(e){i=e instanceof or?e.errors:[e]}var l=this._finalizers;if(l){this._finalizers=null;try{for(var u=Qn(l),d=u.next();!d.done;d=u.next()){var f=d.value;try{dr(f)}catch(e){i??=[],e instanceof or?i=er(er([],$n(i)),$n(e.errors)):i.push(e)}}}catch(e){n={error:e}}finally{try{d&&!d.done&&(r=u.return)&&r.call(u)}finally{if(n)throw n.error}}}if(i)throw new or(i)}},e.prototype.add=function(t){if(t&&t!==this){if(this.closed)dr(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=this._finalizers??[]).push(t)}}},e.prototype._hasParent=function(e){var t=this._parentage;return t===e||Array.isArray(t)&&t.includes(e)},e.prototype._addParent=function(e){var t=this._parentage;this._parentage=Array.isArray(t)?(t.push(e),t):t?[t,e]:e},e.prototype._removeParent=function(e){var t=this._parentage;t===e?this._parentage=null:Array.isArray(t)&&sr(t,e)},e.prototype.remove=function(t){var n=this._finalizers;n&&sr(n,t),t instanceof e&&t._removeParent(this)},e.EMPTY=(function(){var t=new e;return t.closed=!0,t})(),e}(),lr=cr.EMPTY;function ur(e){return e instanceof cr||e&&`closed`in e&&ir(e.remove)&&ir(e.add)&&ir(e.unsubscribe)}function dr(e){ir(e)?e():e.unsubscribe()}var fr={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1},pr={setTimeout:function(e,t){var n=[...arguments].slice(2),r=pr.delegate;return r?.setTimeout?r.setTimeout.apply(r,er([e,t],$n(n))):setTimeout.apply(void 0,er([e,t],$n(n)))},clearTimeout:function(e){return(pr.delegate?.clearTimeout||clearTimeout)(e)},delegate:void 0};function mr(e){pr.setTimeout(function(){var t=fr.onUnhandledError;if(t)t(e);else throw e})}function hr(){}var gr=(function(){return yr(`C`,void 0,void 0)})();function _r(e){return yr(`E`,void 0,e)}function vr(e){return yr(`N`,e,void 0)}function yr(e,t,n){return{kind:e,value:t,error:n}}var br=null;function xr(e){if(fr.useDeprecatedSynchronousErrorHandling){var t=!br;if(t&&(br={errorThrown:!1,error:null}),e(),t){var n=br,r=n.errorThrown,i=n.error;if(br=null,r)throw i}}else e()}function Sr(e){fr.useDeprecatedSynchronousErrorHandling&&br&&(br.errorThrown=!0,br.error=e)}var Cr=function(e){Yn(t,e);function t(t){var n=e.call(this)||this;return n.isStopped=!1,t?(n.destination=t,ur(t)&&t.add(n)):n.destination=jr,n}return t.create=function(e,t,n){return new Dr(e,t,n)},t.prototype.next=function(e){this.isStopped?Ar(vr(e),this):this._next(e)},t.prototype.error=function(e){this.isStopped?Ar(_r(e),this):(this.isStopped=!0,this._error(e))},t.prototype.complete=function(){this.isStopped?Ar(gr,this):(this.isStopped=!0,this._complete())},t.prototype.unsubscribe=function(){this.closed||(this.isStopped=!0,e.prototype.unsubscribe.call(this),this.destination=null)},t.prototype._next=function(e){this.destination.next(e)},t.prototype._error=function(e){try{this.destination.error(e)}finally{this.unsubscribe()}},t.prototype._complete=function(){try{this.destination.complete()}finally{this.unsubscribe()}},t}(cr),wr=Function.prototype.bind;function Tr(e,t){return wr.call(e,t)}var Er=function(){function e(e){this.partialObserver=e}return e.prototype.next=function(e){var t=this.partialObserver;if(t.next)try{t.next(e)}catch(e){Or(e)}},e.prototype.error=function(e){var t=this.partialObserver;if(t.error)try{t.error(e)}catch(e){Or(e)}else Or(e)},e.prototype.complete=function(){var e=this.partialObserver;if(e.complete)try{e.complete()}catch(e){Or(e)}},e}(),Dr=function(e){Yn(t,e);function t(t,n,r){var i=e.call(this)||this,a;if(ir(t)||!t)a={next:t??void 0,error:n??void 0,complete:r??void 0};else{var o;i&&fr.useDeprecatedNextContext?(o=Object.create(t),o.unsubscribe=function(){return i.unsubscribe()},a={next:t.next&&Tr(t.next,o),error:t.error&&Tr(t.error,o),complete:t.complete&&Tr(t.complete,o)}):a=t}return i.destination=new Er(a),i}return t}(Cr);function Or(e){fr.useDeprecatedSynchronousErrorHandling?Sr(e):mr(e)}function kr(e){throw e}function Ar(e,t){var n=fr.onStoppedNotification;n&&pr.setTimeout(function(){return n(e,t)})}var jr={closed:!0,next:hr,error:kr,complete:hr},Mr=(function(){return typeof Symbol==`function`&&Symbol.observable||`@@observable`})();function Nr(e){return e}function Pr(e){return e.length===0?Nr:e.length===1?e[0]:function(t){return e.reduce(function(e,t){return t(e)},t)}}var Fr=function(){function e(e){e&&(this._subscribe=e)}return e.prototype.lift=function(t){var n=new e;return n.source=this,n.operator=t,n},e.prototype.subscribe=function(e,t,n){var r=this,i=Rr(e)?e:new Dr(e,t,n);return xr(function(){var e=r,t=e.operator,n=e.source;i.add(t?t.call(i,n):n?r._subscribe(i):r._trySubscribe(i))}),i},e.prototype._trySubscribe=function(e){try{return this._subscribe(e)}catch(t){e.error(t)}},e.prototype.forEach=function(e,t){var n=this;return t=Ir(t),new t(function(t,r){var i=new Dr({next:function(t){try{e(t)}catch(e){r(e),i.unsubscribe()}},error:r,complete:t});n.subscribe(i)})},e.prototype._subscribe=function(e){return this.source?.subscribe(e)},e.prototype[Mr]=function(){return this},e.prototype.pipe=function(){return Pr([...arguments])(this)},e.prototype.toPromise=function(e){var t=this;return e=Ir(e),new e(function(e,n){var r;t.subscribe(function(e){return r=e},function(e){return n(e)},function(){return e(r)})})},e.create=function(t){return new e(t)},e}();function Ir(e){return e??fr.Promise??Promise}function Lr(e){return e&&ir(e.next)&&ir(e.error)&&ir(e.complete)}function Rr(e){return e&&e instanceof Cr||Lr(e)&&ur(e)}function zr(e){return ir(e?.lift)}function Br(e){return function(t){if(zr(t))return t.lift(function(t){try{return e(t,this)}catch(e){this.error(e)}});throw TypeError(`Unable to lift unknown Observable type`)}}function Vr(e,t,n,r,i){return new Hr(e,t,n,r,i)}var Hr=function(e){Yn(t,e);function t(t,n,r,i,a,o){var s=e.call(this,t)||this;return s.onFinalize=a,s.shouldUnsubscribe=o,s._next=n?function(e){try{n(e)}catch(e){t.error(e)}}:e.prototype._next,s._error=i?function(e){try{i(e)}catch(e){t.error(e)}finally{this.unsubscribe()}}:e.prototype._error,s._complete=r?function(){try{r()}catch(e){t.error(e)}finally{this.unsubscribe()}}:e.prototype._complete,s}return t.prototype.unsubscribe=function(){var t;if(!this.shouldUnsubscribe||this.shouldUnsubscribe()){var n=this.closed;e.prototype.unsubscribe.call(this),!n&&((t=this.onFinalize)==null||t.call(this))}},t}(Cr),Ur=ar(function(e){return function(){e(this),this.name=`ObjectUnsubscribedError`,this.message=`object unsubscribed`}}),Wr=function(e){Yn(t,e);function t(){var t=e.call(this)||this;return t.closed=!1,t.currentObservers=null,t.observers=[],t.isStopped=!1,t.hasError=!1,t.thrownError=null,t}return t.prototype.lift=function(e){var t=new Gr(this,this);return t.operator=e,t},t.prototype._throwIfClosed=function(){if(this.closed)throw new Ur},t.prototype.next=function(e){var t=this;xr(function(){var n,r;if(t._throwIfClosed(),!t.isStopped){t.currentObservers||=Array.from(t.observers);try{for(var i=Qn(t.currentObservers),a=i.next();!a.done;a=i.next())a.value.next(e)}catch(e){n={error:e}}finally{try{a&&!a.done&&(r=i.return)&&r.call(i)}finally{if(n)throw n.error}}}})},t.prototype.error=function(e){var t=this;xr(function(){if(t._throwIfClosed(),!t.isStopped){t.hasError=t.isStopped=!0,t.thrownError=e;for(var n=t.observers;n.length;)n.shift().error(e)}})},t.prototype.complete=function(){var e=this;xr(function(){if(e._throwIfClosed(),!e.isStopped){e.isStopped=!0;for(var t=e.observers;t.length;)t.shift().complete()}})},t.prototype.unsubscribe=function(){this.isStopped=this.closed=!0,this.observers=this.currentObservers=null},Object.defineProperty(t.prototype,"observed",{get:function(){return this.observers?.length>0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(t){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,t)},t.prototype._subscribe=function(e){return this._throwIfClosed(),this._checkFinalizedStatuses(e),this._innerSubscribe(e)},t.prototype._innerSubscribe=function(e){var t=this,n=this,r=n.hasError,i=n.isStopped,a=n.observers;return r||i?lr:(this.currentObservers=null,a.push(e),new cr(function(){t.currentObservers=null,sr(a,e)}))},t.prototype._checkFinalizedStatuses=function(e){var t=this,n=t.hasError,r=t.thrownError,i=t.isStopped;n?e.error(r):i&&e.complete()},t.prototype.asObservable=function(){var e=new Fr;return e.source=this,e},t.create=function(e,t){return new Gr(e,t)},t}(Fr),Gr=function(e){Yn(t,e);function t(t,n){var r=e.call(this)||this;return r.destination=t,r.source=n,r}return t.prototype.next=function(e){var t,n;(n=(t=this.destination)?.next)==null||n.call(t,e)},t.prototype.error=function(e){var t,n;(n=(t=this.destination)?.error)==null||n.call(t,e)},t.prototype.complete=function(){var e,t;(t=(e=this.destination)?.complete)==null||t.call(e)},t.prototype._subscribe=function(e){return this.source?.subscribe(e)??lr},t}(Wr),Kr=function(e){Yn(t,e);function t(t){var n=e.call(this)||this;return n._value=t,n}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(t){var n=e.prototype._subscribe.call(this,t);return!n.closed&&t.next(this._value),n},t.prototype.getValue=function(){var e=this,t=e.hasError,n=e.thrownError,r=e._value;if(t)throw n;return this._throwIfClosed(),r},t.prototype.next=function(t){e.prototype.next.call(this,this._value=t)},t}(Wr);function qr(e){return e[e.length-1]}function Jr(e){return ir(qr(e))?e.pop():void 0}var Yr=(function(e){return e&&typeof e.length==`number`&&typeof e!=`function`});function Xr(e){return ir(e?.then)}function Zr(e){return ir(e[Mr])}function Qr(e){return Symbol.asyncIterator&&ir(e?.[Symbol.asyncIterator])}function $r(e){return TypeError(`You provided `+(typeof e==`object`&&e?`an invalid object`:`'`+e+`'`)+` where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`)}function ei(){return typeof Symbol!=`function`||!Symbol.iterator?`@@iterator`:Symbol.iterator}var ti=ei();function ni(e){return ir(e?.[ti])}function ri(e){return nr(this,arguments,function(){var t,n,r,i;return Zn(this,function(a){switch(a.label){case 0:t=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,tr(t.read())];case 3:return n=a.sent(),r=n.value,i=n.done,i?[4,tr(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,tr(r)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return t.releaseLock(),[7];case 10:return[2]}})})}function ii(e){return ir(e?.getReader)}function ai(e){if(e instanceof Fr)return e;if(e!=null){if(Zr(e))return gee(e);if(Yr(e))return _ee(e);if(Xr(e))return vee(e);if(Qr(e))return oi(e);if(ni(e))return yee(e);if(ii(e))return bee(e)}throw $r(e)}function gee(e){return new Fr(function(t){var n=e[Mr]();if(ir(n.subscribe))return n.subscribe(t);throw TypeError(`Provided object does not correctly implement Symbol.observable`)})}function _ee(e){return new Fr(function(t){for(var n=0;n=0?n.slice(0,r):n}function _i(e,t){return e?t?`${e} ${t}`:e:t||``}var Hee=hi({__forward_ref__:hi});function vi(e){return e.__forward_ref__=vi,e}function yi(e){return bi(e)?e():e}function bi(e){return typeof e==`function`&&Object.hasOwn(e,Hee)&&e.__forward_ref__===vi}function xi(e){return{token:e.token,providedIn:e.providedIn||null,factory:e.factory,value:void 0}}function Si(e){return Ci(e,Ei)}function Ci(e,t){return Object.hasOwn(e,t)&&e[t]||null}function wi(e){return(e?.[Ei]??null)||null}function Ti(e){return e&&Object.hasOwn(e,Di)?e[Di]:null}var Ei=hi({ɵprov:hi}),Di=hi({ɵinj:hi}),Oi=class{_desc;ngMetadataName=`InjectionToken`;ɵprov;constructor(e,t){this._desc=e,this.ɵprov=void 0,typeof t==`number`?this.__NG_ELEMENT_ID__=t:t!==void 0&&(this.ɵprov=xi({token:this,providedIn:t.providedIn||`root`,factory:t.factory}))}get multi(){return this}toString(){return`InjectionToken ${this._desc}`}};function ki(e){return e&&!!e.ɵproviders}var Ai=hi({ɵcmp:hi}),ji=hi({ɵdir:hi}),Mi=hi({ɵpipe:hi}),Ni=hi({ɵfac:hi}),Pi=hi({__NG_ELEMENT_ID__:hi}),Fi=hi({__NG_ENV_ID__:hi});function Ii(e){return zi(e,`@Component`),e[Ai]||null}function Li(e){return zi(e,`@Directive`),e[ji]||null}function Ri(e){return zi(e,`@Pipe`),e[Mi]||null}function zi(e,t){if(e==null)throw new pi(-919,!1)}function Bi(e){return typeof e==`string`?e:e==null?``:String(e)}var Vi=hi({ngErrorCode:hi}),Hi=hi({ngErrorMessage:hi}),Ui=hi({ngTokenPath:hi});function Wi(e,t){return Ki(``,-200,t)}function Gi(e,t){throw new pi(-201,!1)}function Ki(e,t,n){let r=new pi(t,e);return r[Vi]=t,r[Hi]=e,n&&(r[Ui]=n),r}function qi(e){return e[Vi]}var Ji;function Yi(){return Ji}function Xi(e){let t=Ji;return Ji=e,t}function Zi(e,t,n){let r=Si(e);if(r&&r.providedIn==`root`)return r.value===void 0?r.value=r.factory():r.value;if(n&8)return null;if(t!==void 0)return t;Gi(e,``)}var Qi=globalThis,$i={},ea=`__NG_DI_FLAG__`,ta=class{injector;constructor(e){this.injector=e}retrieve(e,t){let n=ia(t)||0;try{return this.injector.get(e,n&8?null:$i,n)}catch(e){if(Hn(e))return e;throw e}}};function na(e,t=0){let n=zn();if(n===void 0)throw new pi(-203,!1);if(n===null)return Zi(e,void 0,t);{let r=aa(t),i=n.retrieve(e,r);if(Hn(i)){if(r.optional)return null;throw i}return i}}function ra(e,t=0){return(Yi()||na)(yi(e),t)}function x(e,t){return ra(e,ia(t))}function ia(e){return e===void 0||typeof e==`number`?e:0|(e.optional&&8)|(e.host&&1)|(e.self&&2)|(e.skipSelf&&4)}function aa(e){return{optional:!!(e&8),host:!!(e&1),self:!!(e&2),skipSelf:!!(e&4)}}function oa(e){let t=[];for(let n=0;nArray.isArray(e)?da(e,t):t(e))}function fa(e,t,n){t>=e.length?e.push(n):e.splice(t,0,n)}function pa(e,t){return t>=e.length-1?e.pop():e.splice(t,1)[0]}function ma(e,t){let n=[];for(let r=0;rt;){let t=i-2;e[i]=e[t],i--}e[t]=n,e[t+1]=r}}function ga(e,t,n){let r=va(e,t);return r>=0?e[r|1]=n:(r=~r,ha(e,r,t,n)),r}function _a(e,t){let n=va(e,t);if(n>=0)return e[n|1]}function va(e,t){return ya(e,t,1)}function ya(e,t,n){let r=0,i=e.length>>n;for(;i!==r;){let a=r+(i-r>>1),o=e[a<t?i=a:r=a+1}return~(i<{n.push(e)};return da(t,e=>{let t=e;ka(t,a,[],r)&&(i||=[],i.push(t))}),i!==void 0&&Oa(i,a),n}function Oa(e,t){for(let n=0;n{t(e,r)})}}function ka(e,t,n,r){if(e=yi(e),!e)return!1;let i=null,a=Ti(e),o=!a&&Ii(e);if(!a&&!o){let t=e.ngModule;if(a=Ti(t),a)i=t;else return!1}else if(o&&!o.standalone)return!1;else i=e;let s=r.has(i);if(o){if(s)return!1;if(r.add(i),o.dependencies){let e=typeof o.dependencies==`function`?o.dependencies():o.dependencies;for(let i of e)ka(i,t,n,r)}}else if(a){if(a.imports!=null&&!s){r.add(i);let e;try{da(a.imports,i=>{ka(i,t,n,r)&&(e||=[],e.push(i))})}finally{}e!==void 0&&Oa(e,t)}if(!s){let e=ca(i)||(()=>new i);t({provide:i,useFactory:e,deps:xa},i),t({provide:wa,useValue:i,multi:!0},i),t({provide:Sa,useValue:()=>ra(i),multi:!0},i)}let o=a.providers;if(o!=null&&!s){let n=e;Aa(o,e=>{t(e,n)})}}else return!1;return i!==e&&e.providers!==void 0}function Aa(e,t){for(let n of e)ki(n)&&(n=n.ɵproviders),Array.isArray(n)?Aa(n,t):t(n)}var ja=hi({provide:String,useValue:hi});function Ma(e){return typeof e==`object`&&!!e&&ja in e}function Na(e){return!!(e&&e.useExisting)}function Pa(e){return!!(e&&e.useFactory)}function Fa(e){return typeof e==`function`}function Ia(e){return!!e.useClass}var La=new Oi(``),Ra={},za={},Ba=void 0;function Va(){return Ba===void 0&&(Ba=new Ta),Ba}var Ha=class{},Ua=class extends Ha{parent;source;scopes;records=new Map;_ngOnDestroyHooks=new Set;_onDestroyHooks=[];get destroyed(){return this._destroyed}_destroyed=!1;injectorDefTypes;constructor(e,t,n,r){super(),this.parent=t,this.source=n,this.scopes=r,$a(e,e=>this.processProvider(e)),this.records.set(Ca,Ya(void 0,this)),r.has(`environment`)&&this.records.set(Ha,Ya(void 0,this));let i=this.records.get(La);i!=null&&typeof i.value==`string`&&this.scopes.add(i.value),this.injectorDefTypes=new Set(this.get(wa,xa,{self:!0}))}retrieve(e,t){let n=ia(t)||0;try{return this.get(e,$i,n)}catch(e){if(Hn(e))return e;throw e}}destroy(){Ja(this),this._destroyed=!0;let e=an(null);try{for(let e of this._ngOnDestroyHooks)e.ngOnDestroy();let e=this._onDestroyHooks;this._onDestroyHooks=[];for(let t of e)t()}finally{this.records.clear(),this._ngOnDestroyHooks.clear(),this.injectorDefTypes.clear(),an(e)}}onDestroy(e){return Ja(this),this._onDestroyHooks.push(e),()=>this.removeOnDestroy(e)}runInContext(e){Ja(this);let t=Bn(this),n=Xi(void 0);try{return e()}finally{Bn(t),Xi(n)}}get(e,t=$i,n){if(Ja(this),Object.hasOwn(e,Fi))return e[Fi](this);let r=ia(n),i=Bn(this),a=Xi(void 0);try{if(!(r&4)){let t=this.records.get(e);if(t===void 0){let n=Qa(e)&&Si(e);t=n&&this.injectableDefInScope(n)?Ya(Wa(e),Ra):null,this.records.set(e,t)}if(t!=null)return this.hydrate(e,t,r)}let n=r&2?Va():this.parent;return t=r&8&&t===$i?null:t,n.get(e,t)}catch(e){let t=qi(e);throw t===-200||t===-201?new pi(t,null):e}finally{Xi(a),Bn(i)}}resolveInjectorInitializers(){let e=an(null),t=Bn(this),n=Xi(void 0);try{let e=this.get(Sa,xa,{self:!0});for(let t of e)t()}finally{Bn(t),Xi(n),an(e)}}toString(){return`R3Injector[...]`}processProvider(e){e=yi(e);let t=Fa(e)?e:yi(e&&e.provide),n=Ka(e);if(!Fa(e)&&e.multi===!0){let n=this.records.get(t);n||(n=Ya(void 0,Ra,!0),n.factory=()=>oa(n.multi),this.records.set(t,n)),t=e,n.multi.push(e)}this.records.set(t,n)}hydrate(e,t,n){let r=an(null);try{if(t.value===za)throw Wi(``);return t.value===Ra&&(t.value=za,t.value=t.factory(void 0,n)),typeof t.value==`object`&&t.value&&Za(t.value)&&this._ngOnDestroyHooks.add(t.value),t.value}finally{an(r)}}injectableDefInScope(e){if(!e.providedIn)return!1;let t=yi(e.providedIn);return typeof t==`string`?t===`any`||this.scopes.has(t):this.injectorDefTypes.has(t)}removeOnDestroy(e){let t=this._onDestroyHooks.indexOf(e);t!==-1&&this._onDestroyHooks.splice(t,1)}};function Wa(e){let t=Si(e),n=t===null?ca(e):t.factory;if(n!==null)return n;if(e instanceof Oi)throw new pi(-204,!1);if(e instanceof Function)return Ga(e);throw new pi(-204,!1)}function Ga(e){if(e.length>0)throw new pi(-204,!1);let t=wi(e);return t===null?()=>new e:()=>t.factory(e)}function Ka(e){return Ma(e)?Ya(void 0,e.useValue):Ya(qa(e),Ra)}function qa(e,t,n){let r;if(Fa(e)){let t=yi(e);return ca(t)||Wa(t)}if(Ma(e))r=()=>yi(e.useValue);else if(Pa(e))r=()=>e.useFactory(...oa(e.deps||[]));else if(Na(e))r=(t,n)=>ra(yi(e.useExisting),n!==void 0&&n&8?8:void 0);else{let t=yi(e&&(e.useClass||e.provide));if(Xa(e))r=()=>new t(...oa(e.deps));else return ca(t)||Wa(t)}return r}function Ja(e){if(e.destroyed)throw new pi(-205,!1)}function Ya(e,t,n=!1){return{factory:e,value:t,multi:n?[]:void 0}}function Xa(e){return!!e.deps}function Za(e){return typeof e==`object`&&!!e&&typeof e.ngOnDestroy==`function`}function Qa(e){return typeof e==`function`||typeof e==`object`&&e.ngMetadataName===`InjectionToken`}function $a(e,t){for(let n of e)Array.isArray(n)?$a(n,t):n&&ki(n)?$a(n.ɵproviders,t):t(n)}function eo(e,t){let n;e instanceof Ua?(Ja(e),n=e):n=new ta(e);let r=Bn(n),i=Xi(void 0);try{return t()}finally{Bn(r),Xi(i)}}function to(){return Yi()!==void 0||zn()!=null}var no=1;function ro(e){return Array.isArray(e)&&typeof e[no]==`object`}function io(e){return Array.isArray(e)&&e[no]===!0}function ao(e){return!!(e.flags&4)}function oo(e){return e.componentOffset>-1}function so(e){return(e.flags&1)==1}function co(e){return!!e.template}function lo(e){return!!(e[2]&512)}function uo(e){return(e[2]&256)==256}var fo=(function(e){return e[e.NONE=0]=`NONE`,e[e.HTML=1]=`HTML`,e[e.STYLE=2]=`STYLE`,e[e.SCRIPT=3]=`SCRIPT`,e[e.URL=4]=`URL`,e[e.RESOURCE_URL=5]=`RESOURCE_URL`,e[e.ATTRIBUTE_NO_BINDING=6]=`ATTRIBUTE_NO_BINDING`,e})(fo||{}),po=`math`;function mo(e){for(;Array.isArray(e);)e=e[0];return e}function ho(e,t){return mo(t[e])}function go(e,t){return mo(t[e.index])}function _o(e,t){return e.data[t]}function vo(e,t){return e[t]}function yo(e,t,n,r){n>=e.data.length&&(e.data[n]=null,e.blueprint[n]=null),t[n]=r}function bo(e,t){let n=t[e];return ro(n)?n:n[0]}function xo(e){return(e[2]&128)==128}function So(e){return io(e[3])}function Co(e,t){return t==null?null:e[t]}function wo(e){e[17]=0}function To(e){e[2]&1024||(e[2]|=1024,xo(e)&&Oo(e))}function Uee(e,t){for(;e>0;)t=t[14],e--;return t}function Eo(e){return!!(e[2]&9216||e[24]?.dirty)}function Do(e){e[10].changeDetectionScheduler?.notify(8),e[2]&64&&(e[2]|=1024),Eo(e)&&Oo(e)}function Oo(e){e[10].changeDetectionScheduler?.notify(0);let t=Ao(e);for(;t!==null&&!(t[2]&8192||(t[2]|=8192,!xo(t)));)t=Ao(t)}function ko(e,t){if(uo(e))throw new pi(911,!1);e[21]===null&&(e[21]=[]),e[21].push(t)}function Wee(e,t){if(e[21]===null)return;let n=e[21].indexOf(t);n!==-1&&e[21].splice(n,1)}function Ao(e){let t=e[3];return io(t)?t[3]:t}function jo(e){return e[7]??=[]}function Mo(e){return e.cleanup??=[]}function Gee(e,t,n,r){let i=jo(t);i.push(n),e.firstCreatePass&&Mo(e).push(r,i.length-1)}var No={lFrame:as(null),bindingsEnabled:!0,skipHydrationRootTNode:null},Po=!1;function Kee(){return No.lFrame.elementDepthCount}function qee(){No.lFrame.elementDepthCount++}function Fo(){No.lFrame.elementDepthCount--}function Io(){return No.bindingsEnabled}function Lo(){return No.skipHydrationRootTNode!==null}function Ro(e){return No.skipHydrationRootTNode===e}function zo(){No.skipHydrationRootTNode=null}function Bo(){return No.lFrame.lView}function Vo(){return No.lFrame.tView}function S(e){return No.lFrame.contextLView=e,e[8]}function C(e){return No.lFrame.contextLView=null,e}function Ho(){let e=Uo();for(;e!==null&&e.type===64;)e=e.parent;return e}function Uo(){return No.lFrame.currentTNode}function Jee(){let e=No.lFrame,t=e.currentTNode;return e.isParent?t:t.parent}function Wo(e,t){let n=No.lFrame;n.currentTNode=e,n.isParent=t}function Go(){return No.lFrame.isParent}function Ko(){No.lFrame.isParent=!1}function qo(){return No.lFrame.contextLView}function Jo(){return Po}function Yo(e){let t=Po;return Po=e,t}function Xo(){let e=No.lFrame,t=e.bindingRootIndex;return t===-1&&(t=e.bindingRootIndex=e.tView.bindingStartIndex),t}function Zo(){return No.lFrame.bindingIndex}function Yee(e){return No.lFrame.bindingIndex=e}function Qo(){return No.lFrame.bindingIndex++}function $o(e){let t=No.lFrame,n=t.bindingIndex;return t.bindingIndex+=e,n}function Xee(){return No.lFrame.inI18n}function Zee(e,t){let n=No.lFrame;n.bindingIndex=n.bindingRootIndex=e,es(t)}function Qee(){return No.lFrame.currentDirectiveIndex}function es(e){No.lFrame.currentDirectiveIndex=e}function $ee(e){let t=No.lFrame.currentDirectiveIndex;return t===-1?null:e[t]}function ete(){return No.lFrame.currentQueryIndex}function ts(e){No.lFrame.currentQueryIndex=e}function tte(e){let t=e[1];return t.type===2?t.declTNode:t.type===1?e[5]:null}function ns(e,t,n){if(n&4){let r=t,i=e;for(;r=r.parent,r===null&&!(n&1)&&(r=tte(i),!(r===null||(i=i[14],r.type&10))););if(r===null)return!1;t=r,e=i}let r=No.lFrame=is();return r.currentTNode=t,r.lView=e,!0}function rs(e){let t=is(),n=e[1];No.lFrame=t,t.currentTNode=n.firstChild,t.lView=e,t.tView=n,t.contextLView=e,t.bindingIndex=n.bindingStartIndex,t.inI18n=!1}function is(){let e=No.lFrame,t=e===null?null:e.child;return t===null?as(e):t}function as(e){let t={currentTNode:null,isParent:!0,lView:null,tView:null,selectedIndex:-1,contextLView:null,elementDepthCount:0,currentNamespace:null,currentDirectiveIndex:-1,bindingRootIndex:-1,bindingIndex:-1,currentQueryIndex:0,parent:e,child:null,inI18n:!1};return e!==null&&(e.child=t),t}function os(){let e=No.lFrame;return No.lFrame=e.parent,e.currentTNode=null,e.lView=null,e}var ss=os;function cs(){let e=os();e.isParent=!0,e.tView=null,e.selectedIndex=-1,e.contextLView=null,e.elementDepthCount=0,e.currentDirectiveIndex=-1,e.currentNamespace=null,e.bindingRootIndex=-1,e.bindingIndex=-1,e.currentQueryIndex=0}function nte(e){return(No.lFrame.contextLView=Uee(e,No.lFrame.contextLView))[8]}function ls(){return No.lFrame.selectedIndex}function us(e){No.lFrame.selectedIndex=e}function ds(){let e=No.lFrame;return _o(e.tView,e.selectedIndex)}function w(){No.lFrame.currentNamespace=`svg`}function fs(){ps()}function ps(){No.lFrame.currentNamespace=null}function ms(){return No.lFrame.currentNamespace}var hs=!0;function gs(){return hs}function _s(e){hs=e}function vs(e,t=null,n=null,r){let i=ys(e,t,n,r);return i.resolveInjectorInitializers(),i}function ys(e,t=null,n=null,r,i=new Set){return new Ua([n||xa,Ea(e)],t||Va(),null,i)}var bs=class e{static THROW_IF_NOT_FOUND=$i;static NULL=new Ta;static create(e,t){if(Array.isArray(e))return vs({name:``},t,e,``);{let t=e.name??``;return vs({name:t},e.parent,e.providers,t)}}static ɵprov=xi({token:e,providedIn:`any`,factory:()=>ra(Ca)});static __NG_ELEMENT_ID__=-1},xs=new Oi(``),Ss=class{static __NG_ELEMENT_ID__=ws;static __NG_ENV_ID__=e=>e},Cs=class extends Ss{_lView;constructor(e){super(),this._lView=e}get destroyed(){return uo(this._lView)}onDestroy(e){let t=this._lView;return ko(t,e),()=>Wee(t,e)}};function ws(){return new Cs(Bo())}var Ts=new Oi(``),Es=(()=>{class e{taskId=0;pendingTasks=new Set;destroyed=!1;pendingTask=new Kr(!1);debugTaskTracker=x(Ts,{optional:!0});get hasPendingTasks(){return!this.destroyed&&this.pendingTask.value}get hasPendingTasksObservable(){return this.destroyed?new Fr(e=>{e.next(!1),e.complete()}):this.pendingTask}add(){!this.hasPendingTasks&&!this.destroyed&&this.pendingTask.next(!0);let e=this.taskId++;return this.pendingTasks.add(e),this.debugTaskTracker?.add(e),e}has(e){return this.pendingTasks.has(e)}remove(e){this.pendingTasks.delete(e),this.debugTaskTracker?.remove(e),this.pendingTasks.size===0&&this.hasPendingTasks&&this.pendingTask.next(!1)}ngOnDestroy(){this.pendingTasks.clear(),this.hasPendingTasks&&this.pendingTask.next(!1),this.destroyed=!0,this.pendingTask.unsubscribe()}static ɵprov=xi({token:e,providedIn:`root`,factory:()=>new e})}return e})(),Ds=class extends Wr{__isAsync;destroyRef=void 0;pendingTasks=void 0;constructor(e=!1){super(),this.__isAsync=e,to()&&(this.destroyRef=x(Ss,{optional:!0})??void 0,this.pendingTasks=x(Es,{optional:!0})??void 0)}emit(e){let t=an(null);try{super.next(e)}finally{an(t)}}subscribe(e,t,n){let r=e,i=t||(()=>null),a=n;if(e&&typeof e==`object`){let t=e;r=t.next?.bind(t),i=t.error?.bind(t),a=t.complete?.bind(t)}this.__isAsync&&(i=this.wrapInTimeout(i),r&&=this.wrapInTimeout(r),a&&=this.wrapInTimeout(a));let o=super.subscribe({next:r,error:i,complete:a});return e instanceof cr&&e.add(o),o}wrapInTimeout(e){return t=>{let n=this.pendingTasks?.add();setTimeout(()=>{try{e(t)}finally{n!==void 0&&this.pendingTasks?.remove(n)}})}}};function Os(...e){}function ks(e){let t,n;function r(){e=Os;try{n!==void 0&&typeof cancelAnimationFrame==`function`&&cancelAnimationFrame(n),t!==void 0&&clearTimeout(t)}catch{}}return t=setTimeout(()=>{e(),r()}),typeof requestAnimationFrame==`function`&&(n=requestAnimationFrame(()=>{e(),r()})),()=>r()}function As(e){return queueMicrotask(()=>e()),()=>{e=Os}}var js=`isAngularZone`,Ms=`isAngularZone_ID`,Ns=0,Ps=class e{hasPendingMacrotasks=!1;hasPendingMicrotasks=!1;isStable=!0;onUnstable=new Ds(!1);onMicrotaskEmpty=new Ds(!1);onStable=new Ds(!1);onError=new Ds(!1);constructor(e){let{enableLongStackTrace:t=!1,shouldCoalesceEventChangeDetection:n=!1,shouldCoalesceRunChangeDetection:r=!1,scheduleInRootZone:i=!1}=e;if(typeof Zone>`u`)throw new pi(908,!1);Zone.assertZonePatched();let a=this;a._nesting=0,a._outer=a._inner=Zone.current,Zone.TaskTrackingZoneSpec&&(a._inner=a._inner.fork(new Zone.TaskTrackingZoneSpec)),t&&Zone.longStackTraceZoneSpec&&(a._inner=a._inner.fork(Zone.longStackTraceZoneSpec)),a.shouldCoalesceEventChangeDetection=!r&&n,a.shouldCoalesceRunChangeDetection=r,a.callbackScheduled=!1,a.scheduleInRootZone=i,Rs(a)}static isInAngularZone(){return typeof Zone<`u`&&Zone.current.get(js)===!0}static assertInAngularZone(){if(!e.isInAngularZone())throw new pi(909,!1)}static assertNotInAngularZone(){if(e.isInAngularZone())throw new pi(909,!1)}run(e,t,n){return this._inner.run(e,t,n)}runTask(e,t,n,r){let i=this._inner,a=i.scheduleEventTask(`NgZoneEvent: `+r,e,Fs,Os,Os);try{return i.runTask(a,t,n)}finally{i.cancelTask(a)}}runGuarded(e,t,n){return this._inner.runGuarded(e,t,n)}runOutsideAngular(e){return this._outer.run(e)}},Fs={};function Is(e){if(e._nesting==0&&!e.hasPendingMicrotasks&&!e.isStable)try{e._nesting++,e.onMicrotaskEmpty.emit(null)}finally{if(e._nesting--,!e.hasPendingMicrotasks)try{e.runOutsideAngular(()=>e.onStable.emit(null))}finally{e.isStable=!0}}}function Ls(e){if(e.isCheckStableRunning||e.callbackScheduled)return;e.callbackScheduled=!0;function t(){ks(()=>{e.callbackScheduled=!1,zs(e),e.isCheckStableRunning=!0,Is(e),e.isCheckStableRunning=!1})}e.scheduleInRootZone?Zone.root.run(()=>{t()}):e._outer.run(()=>{t()}),zs(e)}function Rs(e){let t=()=>{Ls(e)},n=Ns++;e._inner=e._inner.fork({name:`angular`,properties:{[js]:!0,[Ms]:n,[Ms+n]:!0},onInvokeTask:(n,r,i,a,o,s)=>{if(Us(s))return n.invokeTask(i,a,o,s);try{return Bs(e),n.invokeTask(i,a,o,s)}finally{(e.shouldCoalesceEventChangeDetection&&a.type===`eventTask`||e.shouldCoalesceRunChangeDetection)&&t(),Vs(e)}},onInvoke:(n,r,i,a,o,s,c)=>{try{return Bs(e),n.invoke(i,a,o,s,c)}finally{e.shouldCoalesceRunChangeDetection&&!e.callbackScheduled&&!Ws(s)&&t(),Vs(e)}},onHasTask:(t,n,r,i)=>{t.hasTask(r,i),n===r&&(i.change==`microTask`?(e._hasPendingMicrotasks=i.microTask,zs(e),Is(e)):i.change==`macroTask`&&(e.hasPendingMacrotasks=i.macroTask))},onHandleError:(t,n,r,i)=>(t.handleError(r,i),e.runOutsideAngular(()=>e.onError.emit(i)),!1)})}function zs(e){e.hasPendingMicrotasks=!!(e._hasPendingMicrotasks||(e.shouldCoalesceEventChangeDetection||e.shouldCoalesceRunChangeDetection)&&e.callbackScheduled===!0)}function Bs(e){e._nesting++,e.isStable&&(e.isStable=!1,e.onUnstable.emit(null))}function Vs(e){e._nesting--,Is(e)}var Hs=class{hasPendingMicrotasks=!1;hasPendingMacrotasks=!1;isStable=!0;onUnstable=new Ds;onMicrotaskEmpty=new Ds;onStable=new Ds;onError=new Ds;run(e,t,n){return e.apply(t,n)}runGuarded(e,t,n){return e.apply(t,n)}runOutsideAngular(e){return e()}runTask(e,t,n,r){return e.apply(t,n)}};function Us(e){return Gs(e,`__ignore_ng_zone__`)}function Ws(e){return Gs(e,`__scheduler_tick__`)}function Gs(e,t){return!Array.isArray(e)||e.length!==1?!1:e[0]?.data?.[t]===!0}var Ks=class{_console=console;handleError(e){this._console.error(`ERROR`,e)}},qs=new Oi(``,{factory:()=>{let e=x(Ps),t=x(Ha),n;return r=>{e.runOutsideAngular(()=>{t.destroyed&&!n?setTimeout(()=>{throw r}):(n??=t.get(Ks),n.handleError(r))})}}}),Js={provide:Sa,useValue:()=>{x(Ks,{optional:!0})},multi:!0};function T(e,t){let[n,r,i]=mee(e,t?.equal),a=n;return a[rn],a.set=r,a.update=i,a.asReadonly=Ys.bind(a),a}function Ys(){let e=this[rn];if(e.readonlyFn===void 0){let t=()=>this();t[rn]=e,e.readonlyFn=t}return e.readonlyFn}var Xs=new Oi(``,{factory:()=>Zs}),Zs=`ng`,Qs=new Oi(``),$s=new Oi(``,{providedIn:`platform`,factory:()=>`unknown`}),ec=new Oi(``,{factory:()=>x(xs).body?.querySelector(`[ngCspNonce]`)?.getAttribute(`ngCspNonce`)||null}),tc=(()=>{class e{view;node;constructor(e,t){this.view=e,this.node=t}static __NG_ELEMENT_ID__=nc}return e})();function nc(){return new tc(Bo(),Ho())}var rc=class{},ic=new Oi(``,{factory:()=>!0}),ac=new Oi(``),oc=(()=>{class e{static ɵprov=xi({token:e,providedIn:`root`,factory:()=>new sc})}return e})(),sc=class{dirtyEffectCount=0;queues=new Map;add(e){this.enqueue(e),this.schedule(e)}schedule(e){e.dirty&&this.dirtyEffectCount++}remove(e){let t=e.zone,n=this.queues.get(t);n.has(e)&&(n.delete(e),e.dirty&&this.dirtyEffectCount--)}enqueue(e){let t=e.zone;this.queues.has(t)||this.queues.set(t,new Set);let n=this.queues.get(t);n.has(e)||n.add(e)}flush(){for(;this.dirtyEffectCount>0;){let e=!1;for(let[t,n]of this.queues)e||=t===null?this.flushQueue(n):t.run(()=>this.flushQueue(n));e||(this.dirtyEffectCount=0)}}flushQueue(e){let t=!1;for(let n of e)n.dirty&&(this.dirtyEffectCount--,t=!0,n.run());return t}},cc=class{[rn];constructor(e){this[rn]=e}destroy(){this[rn].destroy()}};function E(e,t){let n=t?.injector??x(bs),r=t?.manualCleanup===!0?null:n.get(Ss),i,a=n.get(tc,null,{optional:!0}),o=n.get(rc);return a===null?i=pc(e,n.get(oc),o):(i=fc(a.view,o,e),r instanceof Cs&&r._lView===a.view&&(r=null)),i.injector=n,r!==null&&(i.onDestroyFns=[r.onDestroy(()=>i.destroy())]),new cc(i)}var lc={...In,cleanupFns:void 0,zone:null,onDestroyFns:null,run(){let e=Yo(!1);try{Ln(this)}finally{Yo(e)}},cleanup(){if(!this.cleanupFns?.length)return;let e=an(null);try{for(;this.cleanupFns.length;)this.cleanupFns.pop()()}finally{this.cleanupFns=[],an(e)}}},uc={...lc,consumerMarkedDirty(){this.scheduler.schedule(this),this.notifier.notify(12)},destroy(){if(bn(this),this.onDestroyFns!==null)for(let e of this.onDestroyFns)e();this.cleanup(),this.scheduler.remove(this)}},dc={...lc,consumerMarkedDirty(){this.view[2]|=8192,Oo(this.view),this.notifier.notify(13)},destroy(){if(bn(this),this.onDestroyFns!==null)for(let e of this.onDestroyFns)e();this.cleanup(),this.view[23]?.delete(this)}};function fc(e,t,n){let r=Object.create(dc);return r.view=e,r.zone=typeof Zone<`u`?Zone.current:null,r.notifier=t,r.fn=mc(r,n),e[23]??=new Set,e[23].add(r),r.consumerMarkedDirty(r),r}function pc(e,t,n){let r=Object.create(uc);return r.fn=mc(r,e),r.scheduler=t,r.notifier=n,r.zone=typeof Zone<`u`?Zone.current:null,r.scheduler.add(r),r.notifier.notify(12),r}function mc(e,t){return()=>{t(t=>(e.cleanupFns??=[]).push(t))}}function hc(e){return typeof e==`function`&&e[rn]!==void 0}function gc(e){return hc(e)&&typeof e.set==`function`}var _c=Symbol(`InputSignalNode#UNSET`),vc={...Pn,transformFn:void 0,applyValueToInputSignal(e,t){Mn(e,t)}};function yc(e){return{toString:e}.toString()}var bc=(function(e){return e[e.TemplateCreateStart=0]=`TemplateCreateStart`,e[e.TemplateCreateEnd=1]=`TemplateCreateEnd`,e[e.TemplateUpdateStart=2]=`TemplateUpdateStart`,e[e.TemplateUpdateEnd=3]=`TemplateUpdateEnd`,e[e.LifecycleHookStart=4]=`LifecycleHookStart`,e[e.LifecycleHookEnd=5]=`LifecycleHookEnd`,e[e.OutputStart=6]=`OutputStart`,e[e.OutputEnd=7]=`OutputEnd`,e[e.BootstrapApplicationStart=8]=`BootstrapApplicationStart`,e[e.BootstrapApplicationEnd=9]=`BootstrapApplicationEnd`,e[e.BootstrapComponentStart=10]=`BootstrapComponentStart`,e[e.BootstrapComponentEnd=11]=`BootstrapComponentEnd`,e[e.ChangeDetectionStart=12]=`ChangeDetectionStart`,e[e.ChangeDetectionEnd=13]=`ChangeDetectionEnd`,e[e.ChangeDetectionSyncStart=14]=`ChangeDetectionSyncStart`,e[e.ChangeDetectionSyncEnd=15]=`ChangeDetectionSyncEnd`,e[e.AfterRenderHooksStart=16]=`AfterRenderHooksStart`,e[e.AfterRenderHooksEnd=17]=`AfterRenderHooksEnd`,e[e.ComponentStart=18]=`ComponentStart`,e[e.ComponentEnd=19]=`ComponentEnd`,e[e.DeferBlockStateStart=20]=`DeferBlockStateStart`,e[e.DeferBlockStateEnd=21]=`DeferBlockStateEnd`,e[e.DynamicComponentStart=22]=`DynamicComponentStart`,e[e.DynamicComponentEnd=23]=`DynamicComponentEnd`,e[e.HostBindingsUpdateStart=24]=`HostBindingsUpdateStart`,e[e.HostBindingsUpdateEnd=25]=`HostBindingsUpdateEnd`,e})(bc||{}),xc=class{previousValue;currentValue;firstChange;constructor(e,t,n){this.previousValue=e,this.currentValue=t,this.firstChange=n}isFirstChange(){return this.firstChange}};function Sc(e,t,n,r){t===null?e[n]=r:t.applyValueToInputSignal(t,r)}var Cc=null,wc=(()=>{Cc=Ec;let e=()=>Ec;return e.ngInherit=!0,e})();function Tc(){return Cc}function Ec(e){return e.type.prototype.ngOnChanges&&(e.setInput=Oc),Dc}function Dc(){let e=Ac(this),t=e?.current;if(t){let n=e.previous;if(n===ba)e.previous=t;else for(let e in t)n[e]=t[e];e.current=null,this.ngOnChanges(t)}}function Oc(e,t,n,r,i){let a=this.declaredInputs[r],o=Ac(e)||jc(e,{previous:ba,current:null}),s=o.current||={},c=o.previous,l=c[a];s[a]=new xc(l&&l.currentValue,n,c===ba),Sc(e,t,i,n)}var kc=`__ngSimpleChanges__`;function Ac(e){return Object.hasOwn(e,kc)&&e[kc]||null}function jc(e,t){return e[kc]=t}var Mc=[],Nc=function(e,t=null,n){for(let r=0;r=r)break}else t[c]<0&&(e[17]+=65536),(s>14>16&&(e[2]&3)===t&&(e[2]+=16384,Bc(o,a)):Bc(o,a)}var Hc=-1,Uc=class{factory;name;injectImpl;resolving=!1;canSeeViewProviders;multi;componentProviders;index;providerFactory;constructor(e,t,n,r){this.factory=e,this.name=r,this.canSeeViewProviders=t,this.injectImpl=n}};function Wc(e){return!!(e.flags&8)}function Gc(e){return!!(e.flags&16)}function Kc(e,t,n){let r=0;for(;rt){o=a-1;break}}}for(;a>16}function el(e,t){let n=$c(e),r=t;for(;n>0;)r=r[14],n--;return r}var tl=!0;function nl(e){let t=tl;return tl=e,t}var rl=255,il=5,al=0,ol={};function sl(e,t,n){let r;typeof n==`string`?r=n.charCodeAt(0)||0:Object.hasOwn(n,Pi)&&(r=n[Pi]),r??=n[Pi]=al++;let i=r&rl,a=1<>il)]|=a}function cl(e,t){let n=ul(e,t);if(n!==-1)return n;let r=t[1];r.firstCreatePass&&(e.injectorIndex=t.length,ll(r.data,e),ll(t,null),ll(r.blueprint,null));let i=dl(e,t),a=e.injectorIndex;if(Zc(i)){let e=Qc(i),n=el(i,t),r=n[1].data;for(let i=0;i<8;i++)t[a+i]=n[e+i]|r[e+i]}return t[a+8]=i,a}function ll(e,t){e.push(0,0,0,0,0,0,0,0,t)}function ul(e,t){return e.injectorIndex===-1||e.parent&&e.parent.injectorIndex===e.injectorIndex||t[e.injectorIndex+8]===null?-1:e.injectorIndex}function dl(e,t){if(e.parent&&e.parent.injectorIndex!==-1)return e.parent.injectorIndex;let n=0,r=null,i=t;for(;i!==null;){if(r=Ol(i),r===null)return Hc;if(n++,i=i[14],r.injectorIndex!==-1)return r.injectorIndex|n<<16}return Hc}function fl(e,t,n){sl(e,t,n)}function pl(e,t,n){if(n&8||e!==void 0)return e;Gi(t,`NodeInjector`)}function ml(e,t,n,r){if(n&8&&r===void 0&&(r=null),!(n&3)){let i=e[9],a=Xi(void 0);try{return i?i.get(t,r,n&8):Zi(t,r,n&8)}finally{Xi(a)}}return pl(r,t,n)}function hl(e,t,n,r=0,i){if(e!==null){if(t[2]&2048&&!(r&2)){let i=Dl(e,t,n,r,ol);if(i!==ol)return i}let i=gl(e,t,n,r,ol);if(i!==ol)return i}return ml(t,n,r,i)}function gl(e,t,n,r,i){let a=bl(n);if(typeof a==`function`){if(!ns(t,e,r))return r&1?pl(i,n,r):ml(t,n,r,i);try{let e;if(e=a(r),e==null&&!(r&8))Gi(n);else return e}finally{ss()}}else if(typeof a==`number`){let i=null,o=ul(e,t),s=Hc,c=r&1?t[15][5]:null;for((o===-1||r&4)&&(s=o===-1?dl(e,t):t[o+8],s===Hc||!Sl(r,!1)?o=-1:(i=t[1],o=Qc(s),t=el(s,t)));o!==-1;){let e=t[1];if(xl(a,o,e.data)){let e=_l(o,t,n,i,r,c);if(e!==ol)return e}s=t[o+8],s!==Hc&&Sl(r,t[1].data[o+8]===c)&&xl(a,o,t)?(i=e,o=Qc(s),t=el(s,t)):o=-1}}return i}function _l(e,t,n,r,i,a){let o=t[1],s=o.data[e+8],c=vl(s,o,n,r==null?oo(s)&&tl:r!=o&&!!(s.type&3),i&1&&a===s);return c===null?ol:yl(t,o,c,s,i)}function vl(e,t,n,r,i){let a=e.providerIndexes,o=t.data,s=a&1048575,c=e.directiveStart,l=e.directiveEnd,u=a>>20,d=r?s:s+u,f=i?s+u:l;for(let e=d;e=c&&t.type===n)return e}if(i){let e=o[c];if(e&&co(e)&&e.type===n)return c}return null}function yl(e,t,n,r,i){let a=e[n],o=t.data;if(a instanceof Uc){let s=a;if(s.resolving)throw Wi(``);let c=nl(s.canSeeViewProviders);s.resolving=!0,o[n].type||o[n];let l=s.injectImpl?Xi(s.injectImpl):null;ns(e,r,0);try{a=e[n]=s.factory(void 0,i,o,e,r),t.firstCreatePass&&n>=r.directiveStart&&Pc(n,o[n],t)}finally{l!==null&&Xi(l),nl(c),s.resolving=!1,ss()}}return a}function bl(e){if(typeof e==`string`)return e.charCodeAt(0)||0;let t=Object.hasOwn(e,Pi)?e[Pi]:void 0;return typeof t==`number`?t>=0?t&rl:wl:t}function xl(e,t,n){let r=1<>il)]&r)}function Sl(e,t){return!(e&2)&&!(e&1&&t)}var Cl=class{_tNode;_lView;constructor(e,t){this._tNode=e,this._lView=t}get(e,t,n){return hl(this._tNode,this._lView,e,ia(n),t)}};function wl(){return new Cl(Ho(),Bo())}function Tl(e){return yc(()=>{let t=e.prototype.constructor,n=t[Ni]||El(t),r=Object.prototype,i=Object.getPrototypeOf(e.prototype).constructor;for(;i&&i!==r;){let e=i[Ni]||El(i);if(e&&e!==n)return e;i=Object.getPrototypeOf(i)}return e=>new e})}function El(e){return bi(e)?()=>{let t=El(yi(e));return t&&t()}:ca(e)}function Dl(e,t,n,r,i){let a=e,o=t;for(;a!==null&&o!==null&&o[2]&2048&&!lo(o);){let e=gl(a,o,n,r|2,ol);if(e!==ol)return e;r&=-5;let t=a.parent;if(!t){let e=o[20];if(e){let t=e.get(n,ol,r);if(t!==ol)return t}t=Ol(o),o=o[14]}a=t}return i}function Ol(e){let t=e[1],n=t.type;return n===2?t.declTNode:n===1?e[5]:null}function kl(e){return{token:e.token,providedIn:e.autoProvided===!1?null:`root`,factory:e.factory,value:void 0}}function Al(){return jl(Ho(),Bo())}function jl(e,t){return new Ml(go(e,t))}var Ml=(()=>{class e{nativeElement;constructor(e){this.nativeElement=e}static __NG_ELEMENT_ID__=Al}return e})();function Nl(e){return e instanceof Ml?e.nativeElement:e}function Pl(){return this._results[Symbol.iterator]()}var Fl=class{_emitDistinctChangesOnly;dirty=!0;_onDirty=void 0;_results=[];_changesDetected=!1;_changes=void 0;length=0;first=void 0;last=void 0;get changes(){return this._changes??=new Wr}constructor(e=!1){this._emitDistinctChangesOnly=e}get(e){return this._results[e]}map(e){return this._results.map(e)}filter(e){return this._results.filter(e)}find(e){return this._results.find(e)}reduce(e,t){return this._results.reduce(e,t)}forEach(e){this._results.forEach(e)}some(e){return this._results.some(e)}toArray(){return this._results.slice()}toString(){return this._results.toString()}reset(e,t){this.dirty=!1;let n=ua(e);(this._changesDetected=!la(this._results,n,t))&&(this._results=n,this.length=n.length,this.last=n[this.length-1],this.first=n[0])}notifyOnChanges(){this._changes!==void 0&&(this._changesDetected||!this._emitDistinctChangesOnly)&&this._changes.next(this)}onDirty(e){this._onDirty=e}setDirty(){this.dirty=!0,this._onDirty?.()}destroy(){this._changes!==void 0&&(this._changes.complete(),this._changes.unsubscribe())}[Symbol.iterator]=Pl};function Il(e){return(e.flags&128)==128}var Ll=(function(e){return e[e.OnPush=0]=`OnPush`,e[e.Eager=1]=`Eager`,e[e.Default=1]=`Default`,e})(Ll||{}),Rl=new Map,zl=0;function Bl(){return zl++}function Vl(e){Rl.set(e[19],e)}function Hl(e){Rl.delete(e[19])}var Ul=`__ngContext__`;function Wl(e,t){ro(t)?(e[Ul]=t[19],Vl(t)):e[Ul]=t}function Gl(e){return ql(e[12])}function Kl(e){return ql(e[4])}function ql(e){for(;e!==null&&!io(e);)e=e[4];return e}var Jl=void 0;function Yl(e){Jl=e}function Xl(){if(Jl!==void 0)return Jl;if(typeof document<`u`)return document;throw new pi(210,!1)}var Zl=!1,Ql=new Oi(``,{factory:()=>Zl}),$l=new WeakMap;function eu(e,t){if(typeof e!=`object`||!e)return;let n=$l.get(e);n||(n=new WeakSet,$l.set(e,n)),n.add(t)}function tu(e){return(e.flags&32)==32}var nu=()=>null;function ru(e,t,n=!1){return nu(e,t,n)}function iu(e,t){let n=e.contentQueries;if(n!==null){let r=an(null);try{for(let r=0;r|^->||--!>|)/g,gu=`​$1​`;function _u(e){return e.replace(mu,e=>e.replace(hu,gu))}function vu(e,t){return e.createText(t)}function yu(e,t,n){e.setValue(t,n)}function bu(e,t){return e.createComment(_u(t))}function xu(e,t,n){return e.createElement(t,n)}function Su(e,t,n,r,i){e.insertBefore(t,n,r,i)}function Cu(e,t,n){e.appendChild(t,n)}function wu(e,t,n,r,i){r===null?Cu(e,t,n):Su(e,t,n,r,i)}function Tu(e,t,n,r){e.removeChild(null,t,n,r)}function rte(e,t,n){e.setAttribute(t,`style`,n)}function ite(e,t,n){n===``?e.removeAttribute(t,`class`):e.setAttribute(t,`class`,n)}function Eu(e,t,n){let{mergedAttrs:r,classes:i,styles:a}=n;r!==null&&Kc(e,t,r),i!==null&&ite(e,t,i),a!==null&&rte(e,t,a)}function Du(e){let t=ate();return t?t.sanitize(fo.URL,e)||``:uu(e,`URL`)?lu(e):pu(Bi(e))}function ate(){let e=Bo();return e&&e[10].sanitizer}function Ou(e){return e.ownerDocument.defaultView}function ku(e){return e.ownerDocument}function ote(e,t,n){let r=e.length;for(;;){let i=e.indexOf(t,n);if(i===-1)return i;if(i===0||e.charCodeAt(i-1)<=32){let n=t.length;if(i+n===r||e.charCodeAt(i+n)<=32)return i}n=i+1}}var Au=`ng-template`;function ste(e,t,n,r){let i=0;if(r){for(;i-1){let e;for(;++ia?``:i[u+1].toLowerCase(),r&2&&l!==e){if(Mu(r))return!1;o=!0}}}}}return Mu(r)||o}function Mu(e){return!(e&1)}function ute(e,t,n,r){if(t===null)return-1;let i=0;if(r||!n){let n=!1;for(;i-1)for(n++;n0?`="`+t+`"`:``)+`]`}else r&8?i+=`.`+o:r&4&&(i+=` `+o)}else i!==``&&!Mu(o)&&(t+=Pu(a,i),i=``),r=o,a||=!Mu(r);n++}return i!==``&&(t+=Pu(a,i)),t}function gte(e){return e.map(hte).join(`,`)}function _te(e){let t=[],n=[],r=1,i=2;for(;r!1});function Ru(e){if(!e)return 0;let t=e.toLowerCase().indexOf(`ms`)>-1?1:1e3;return parseFloat(e)*t}function zu(e,t){return e.getPropertyValue(t).split(`,`).map(e=>e.trim())}function bte(e){let t=zu(e,`transition-property`),n=zu(e,`transition-duration`),r=zu(e,`transition-delay`),i={propertyName:``,duration:0,animationName:void 0};for(let e=0;ei.duration&&(i.propertyName=t[e],i.duration=a)}return i}function xte(e){let t=zu(e,`animation-name`),n=zu(e,`animation-delay`),r=zu(e,`animation-duration`),i=zu(e,`animation-iteration-count`),a={animationName:``,propertyName:void 0,duration:0};for(let e=0;ea.duration&&s!==`infinite`&&(a.animationName=t[e],a.duration=o)}return a}function Bu(e,t){return e!==void 0&&e.duration>t.duration}function Vu(e){return(e.animationName!=null||e.propertyName!=null)&&e.duration>0}function Hu(e){let t=e.effect?.getTiming();if(t===void 0)return;let n=typeof t.duration==`number`?t.duration:0,r=(t.delay??0)+n,i=e.playbackRate;return i!==void 0&&i!==0&&i!==1&&(r/=Math.abs(i)),r}function Ste(e,t){let n=getComputedStyle(e),r=xte(n),i=bte(n),a=r.duration>i.duration?r:i;Bu(t.get(e),a)||Vu(a)&&t.set(e,a)}function Cte(e,t,n){if(!n)return;let r=e.getAnimations();return r.length===0?Ste(e,t):wte(e,t,r)}function wte(e,t,n){let r={animationName:void 0,propertyName:void 0,duration:0};for(let e of n){if(e.effect?.getTiming()?.iterations===1/0)continue;let t=Hu(e)??0,n,i;e.animationName?i=e.animationName:n=e.transitionProperty,t>=r.duration&&(r={animationName:i,propertyName:n,duration:t})}Bu(t.get(e),r)||Vu(r)&&t.set(e,r)}var Uu=new Set,Tte=!1,Ete=1,Wu=typeof document<`u`&&typeof document?.documentElement?.getAnimations==`function`;function Dte(e){return e[9].get(yte,Tte)}function Ote(e,t,n){let r=Ku.get(e);if(r){for(let e of t)r.classList.push(e);for(let e of n)r.cleanupFns.push(e)}else Ku.set(e,{classList:t,cleanupFns:n})}function Gu(e){let t=Ku.get(e);if(t){for(let e of t.cleanupFns)e();Ku.delete(e)}qu.delete(e)}var Ku=new WeakMap,qu=new WeakMap,Ju=new WeakMap;function Yu(e){return e?e[14]??e:null}var Xu=new WeakSet;function kte(e,t,n){let r=Ju.get(e);if(!r||r.length===0)return;let i=t.parentNode,a=t.previousSibling,o=Yu(n);for(let e=r.length-1;e>=0;e--){let{el:n,declarationView:s}=r[e],c=n.parentNode;n===t?(r.splice(e,1),Xu.add(n),n.dispatchEvent(new CustomEvent(`animationend`,{detail:{cancel:!0}}))):(a&&n===a||c&&i&&c!==i&&(o===null||s===null||o===s))&&(r.splice(e,1),n.dispatchEvent(new CustomEvent(`animationend`,{detail:{cancel:!0}})),n.parentNode?.removeChild(n))}}function Ate(e,t,n){let r=Yu(n),i=Ju.get(e);i?i.some(e=>e.el===t)||i.push({el:t,declarationView:r}):Ju.set(e,[{el:t,declarationView:r}])}function Zu(e){let t=e[26]??={};return t.enter??=new Map}function jte(e){let t=typeof e==`function`?e():e,n=Array.isArray(t)?t:null;return typeof t==`string`&&(n=t.trim().split(/\s+/).filter(e=>e)),n}function Qu(e){return e.composedPath?e.composedPath()[0]:e.target}function $u(e,t){let n=qu.get(t);if(n===void 0)return!0;if(t!==Qu(e))return!1;let r=e.animation;if(r){let e=Hu(r);if(e!==void 0&&e+Ete{class e{impl=null;execute(){this.impl?.execute()}static ɵprov=xi({token:e,providedIn:`root`,factory:()=>new e})}return e})(),ad=[0,1,2,3],od=(()=>{class e{ngZone=x(Ps);scheduler=x(rc);errorHandler=x(Ks,{optional:!0});sequences=new Set;deferredRegistrations=new Set;executing=!1;constructor(){x(td,{optional:!0})}execute(){let e=this.sequences.size>0;e&&Nc(bc.AfterRenderHooksStart),this.executing=!0;for(let e of ad)for(let t of this.sequences)if(!t.erroredOrDestroyed&&t.hooks[e])try{t.pipelinedValue=this.ngZone.runOutsideAngular(()=>this.maybeTrace(()=>{let n=t.hooks[e];return n(t.pipelinedValue)},t.snapshot))}catch(e){t.erroredOrDestroyed=!0,this.errorHandler?.handleError(e)}this.executing=!1;for(let e of this.sequences)e.afterRun(),e.once&&(this.sequences.delete(e),e.destroy());for(let e of this.deferredRegistrations)this.sequences.add(e);this.deferredRegistrations.size>0&&this.scheduler.notify(7),this.deferredRegistrations.clear(),e&&Nc(bc.AfterRenderHooksEnd)}register(e){let{view:t}=e;t===void 0?this.executing?this.deferredRegistrations.add(e):this.addSequence(e):((t[25]??=[]).push(e),Oo(t),t[2]|=8192)}addSequence(e){this.sequences.add(e),this.scheduler.notify(7)}unregister(e){this.executing&&this.sequences.has(e)?(e.erroredOrDestroyed=!0,e.pipelinedValue=void 0,e.once=!0):(this.sequences.delete(e),this.deferredRegistrations.delete(e))}maybeTrace(e,t){return t?t.run(ed.AFTER_NEXT_RENDER,e):e()}static ɵprov=xi({token:e,providedIn:`root`,factory:()=>new e})}return e})(),sd=class{impl;hooks;view;once;snapshot;erroredOrDestroyed=!1;pipelinedValue=void 0;unregisterOnDestroy;constructor(e,t,n,r,i,a=null){this.impl=e,this.hooks=t,this.view=n,this.once=r,this.snapshot=a,this.unregisterOnDestroy=i?.onDestroy(()=>this.destroy())}afterRun(){this.erroredOrDestroyed=!1,this.pipelinedValue=void 0,this.snapshot?.dispose(),this.snapshot=null}destroy(){this.impl.unregister(this),this.unregisterOnDestroy?.();let e=this.view?.[25];e&&(this.view[25]=e.filter(e=>e!==this))}};function cd(e,t){let n=t?.injector??x(bs);return rd(`NgAfterNextRender`),Pte(e,n,t,!0)}function Nte(e){return e instanceof Function?[void 0,void 0,e,void 0]:[e.earlyRead,e.write,e.mixedReadWrite,e.read]}function Pte(e,t,n,r){let i=t.get(id);i.impl??=t.get(od);let a=t.get(td,null,{optional:!0}),o=n?.manualCleanup===!0?null:t.get(Ss),s=t.get(tc,null,{optional:!0}),c=new sd(i.impl,Nte(e),s?.view,r,o,a?.snapshot(null));return i.impl.register(c),c}var ld=new Oi(``,{factory:()=>{let e=x(Ha),t=new Set;return e.onDestroy(()=>t.clear()),{queue:t,isScheduled:!1,scheduler:null,injector:e}}});function ud(e,t,n){let r=e.get(ld);if(Array.isArray(t))for(let e of t)r.queue.add(e),n?.detachedLeaveAnimationFns?.push(e);else r.queue.add(t),n?.detachedLeaveAnimationFns?.push(t);r.scheduler&&r.scheduler(e)}function Fte(e,t){let n=e.get(ld);if(Array.isArray(t))for(let e of t)n.queue.delete(e);else n.queue.delete(t)}function Ite(e,t){let n=e.get(ld);if(t.detachedLeaveAnimationFns){for(let e of t.detachedLeaveAnimationFns)n.queue.delete(e);t.detachedLeaveAnimationFns=void 0}}function Lte(e){let t=e.get(ld);t.isScheduled||=(cd(()=>{t.isScheduled=!1;for(let e of t.queue)e();t.queue.clear()},{injector:t.injector}),!0)}function Rte(e){let t=e.get(ld);t.scheduler=Lte,t.scheduler(e)}function dd(e,t){for(let[n,r]of t)ud(e,r.animateFns)}function fd(e,t,n,r){let i=e?.[26]?.enter;t!==null&&i&&i.has(n.index)&&dd(r,i)}function pd(e,t,n,r){try{n.get(Ca)}catch{return r(!1)}let i=e?.[26];i?.enter?.has(t.index)&&Fte(n,i.enter.get(t.index).animateFns);let a=zte(e,t,i);if(a.size===0){let n=!1;if(e){let r=[];md(e,t,r),n=r.length>0}if(!n)return r(!1)}e&&Uu.add(e[19]),ud(n,()=>Bte(e,t,i||void 0,a,r),i||void 0)}function zte(e,t,n){let r=new Map,i=n?.leave;if(i&&i.has(t.index)&&r.set(t.index,i.get(t.index)),e&&i)for(let[n,a]of i){if(r.has(n))continue;let i=e[1].data[n].parent;for(;i;){if(i===t){r.set(n,a);break}i=i.parent}}return r}function Bte(e,t,n,r,i){let a=[];if(n&&n.leave)for(let[e]of r){if(!n.leave.has(e))continue;let t=n.leave.get(e);for(let e of t.animateFns){let{promise:t}=e();a.push(t)}n.detachedLeaveAnimationFns=void 0}if(e&&md(e,t,a),a.length>0){let t=n||e?.[26];if(t){let n=t.running;n&&a.push(n),t.running=Promise.allSettled(a),Hte(e,t.running,i)}else Promise.allSettled(a).then(()=>{e&&Uu.delete(e[19]),i(!0)})}else e&&Uu.delete(e[19]),i(!1)}function md(e,t,n){if(t.type&12){let r=e[t.index];if(io(r))for(let e=10;e{e[26]?.running===t&&(e[26].running=void 0,Uu.delete(e[19])),n(!0)})}function hd(e,t,n,r,i,a,o,s){if(i!=null){let c,l=!1;io(i)?c=i:ro(i)&&(l=!0,i=i[0]);let u=mo(i);e===0&&r!==null?(fd(s,r,a,n),o==null?Cu(t,r,u):Su(t,r,u,o||null,!0)):e===1&&r!==null?(fd(s,r,a,n),Su(t,r,u,o||null,!0),kte(a,u,s)):e===2?(s?.[26]?.leave?.has(a.index)&&Ate(a,u,s),Xu.delete(u),pd(s,a,n,e=>{if(Xu.has(u)){Xu.delete(u);return}Tu(t,u,l,e)})):e===3&&(Xu.delete(u),pd(s,a,n,()=>{t.destroyNode(u)})),c!=null&&kd(t,e,n,c,a,r,o)}}function Ute(e,t){gd(e,t),t[0]=null,t[5]=null}function Wte(e,t,n,r,i,a){r[0]=i,r[5]=t,Dd(e,r,n,1,i,a)}function gd(e,t){t[10].changeDetectionScheduler?.notify(9),Dd(e,t,t[11],2,null,null)}function Gte(e){let t=e[12];if(!t)return yd(e[1],e);for(;t;){let n=null;if(ro(t))n=t[12];else{let e=t[10];e&&(n=e)}if(!n){for(;t&&!t[4]&&t!==e;)ro(t)&&yd(t[1],t),t=t[3];t===null&&(t=e),ro(t)&&yd(t[1],t),n=t&&t[4]}t=n}}function _d(e,t){let n=e[9],r=n.indexOf(t);n.splice(r,1)}function vd(e,t){if(uo(t))return;let n=t[11];n.destroyNode&&Dd(e,t,n,3,null,null),Gte(t)}function yd(e,t){if(uo(t))return;let n=an(null);try{t[2]&=-129,t[2]|=256,t[24]&&bn(t[24]),qte(e,t),Kte(e,t),t[1].type===1&&t[11].destroy();let n=t[16];if(n!==null&&io(t[3])){n!==t[3]&&_d(n,t);let r=t[18];r!==null&&r.detachView(e)}Hl(t)}finally{an(n)}}function Kte(e,t){let n=e.cleanup,r=t[7];if(n!==null)for(let e=0;e=0?r[t]():r[-t].unsubscribe(),e+=2}else{let t=r[n[e+1]];n[e].call(t)}r!==null&&(t[7]=null);let i=t[21];if(i!==null){t[21]=null;for(let e=0;e27&&zd(e,t,27,!1),Nc(o?bc.TemplateUpdateStart:bc.TemplateCreateStart,i,n),n(r,i)}finally{us(a),Nc(o?bc.TemplateUpdateEnd:bc.TemplateCreateEnd,i,n)}}function Ud(e,t,n){Xd(e,t,n),(n.flags&64)==64&&Zd(e,t,n)}function Wd(e,t,n=go){let r=t.localNames;if(r!==null){let i=t.index+1;for(let a=0;a=s&&i<=c){let o=t.data[i],s=r[e+1];Vd(o,n[i],s,a),l=!0}else if(i>c)break}}return o!==null&&Object.hasOwn(r.inputs,i)&&(Vd(r,n[o],i,a),l=!0),l}function lf(e,t){let n=bo(t,e),r=n[1];uf(r,n);let i=n[0];i!==null&&n[6]===null&&(n[6]=ru(i,n[9])),Nc(bc.ComponentStart);try{df(r,n,n[8])}finally{Nc(bc.ComponentEnd,n[8])}}function uf(e,t){for(let n=t.length;n{Oo(e.lView)},consumerOnSignalRead(){this.lView[24]=this}};function Cf(e){let t=e[24]??Object.create(wf);return t.lView=e,t}var wf={...sn,consumerIsAlwaysLive:!0,kind:`template`,consumerMarkedDirty:e=>{let t=Ao(e.lView);for(;t&&!Tf(t[1]);)t=Ao(t);t&&To(t)},consumerOnSignalRead(){this.lView[24]=this}};function Tf(e){return e.type!==2}function Ef(e){if(e[23]===null)return;let t=!0;for(;t;){let n=!1;for(let t of e[23])if(t.dirty&&(n=!0,t.zone===null||Zone.current===t.zone?t.run():t.zone.run(()=>t.run()),e[23]===null))return;t=n&&!!(e[2]&8192)}}var Df=100;function Of(e,t=0){let n=e[10].rendererFactory;n.begin?.();try{kf(e,t)}finally{n.end?.()}}function kf(e,t){let n=Jo();try{Yo(!0),Ff(e,t);let n=0;for(;Eo(e);){if(n===Df)throw new pi(103,!1);n++,Ff(e,1)}}finally{Yo(n)}}function Af(e,t,n,r){if(uo(t))return;let i=t[2];rs(t);let a=!0,o=null,s=null;Tf(e)?(s=yf(t),o=hn(s)):on()===null?(a=!1,s=Cf(t),o=hn(s)):t[24]&&=(bn(t[24]),null);try{wo(t),Yee(e.bindingStartIndex),n!==null&&Hd(e,t,n,2,r);let a=(i&3)==3;if(a){let n=e.preOrderCheckHooks;n!==null&&Ic(t,n,null)}else{let n=e.preOrderHooks;n!==null&&Lc(t,n,0,null),Rc(t,0)}if(Mf(t),Ef(t),jf(t,0),e.contentQueries!==null&&iu(e,t),a){let n=e.contentCheckHooks;n!==null&&Ic(t,n)}else{let n=e.contentHooks;n!==null&&Lc(t,n,1),Rc(t,1)}Lf(e,t);let o=e.components;o!==null&&If(t,o,0);let s=e.viewQuery;if(s!==null&&au(2,s,r),a){let n=e.viewCheckHooks;n!==null&&Ic(t,n)}else{let n=e.viewHooks;n!==null&&Lc(t,n,2),Rc(t,2)}if(e.firstUpdatePass===!0&&(e.firstUpdatePass=!1),t[22]){for(let e of t[22])e();t[22]=null}_f(t),t[2]&=-73}catch(e){throw Oo(t),e}finally{s!==null&&(_n(s,o),a&&xf(s)),cs()}}function jf(e,t){for(let n=Gl(e);n!==null;n=Kl(n))for(let e=10;e0&&(e[n-1][4]=r[4]);let a=pa(e,10+t);Ute(r[1],r);let o=a[18];o!==null&&o.detachView(a[1]),r[3]=null,r[4]=null,r[2]&=-129}return r}function Wf(e,t,n,r){let i=10+r,a=n.length;r>0&&(n[i-1][4]=t),r-1&&(Uf(e,n),pa(t,n))}this._attachedToViewContainer=!1}vd(this._lView[1],this._lView)}onDestroy(e){ko(this._lView,e)}markForCheck(){Rf(this._cdRefInjectingView||this._lView,4)}detach(){this._lView[2]&=-129}reattach(){Do(this._lView),this._lView[2]|=128}detectChanges(){this._lView[2]|=1024,Of(this._lView)}checkNoChanges(){}attachToViewContainerRef(){if(this._appRef)throw new pi(902,!1);this._attachedToViewContainer=!0}detachFromAppRef(){this._appRef=null;let e=lo(this._lView),t=this._lView[16];t!==null&&!e&&_d(t,this._lView),gd(this._lView[1],this._lView)}attachToAppRef(e){if(this._attachedToViewContainer)throw new pi(902,!1);this._appRef=e;let t=lo(this._lView),n=this._lView[16];n!==null&&!t&&Gf(n,this._lView),Do(this._lView)}},qf=(()=>{class e{_declarationLView;_declarationTContainer;elementRef;static __NG_ELEMENT_ID__=Jf;constructor(e,t,n){this._declarationLView=e,this._declarationTContainer=t,this.elementRef=n}get ssrId(){return this._declarationTContainer.tView?.ssrId||null}createEmbeddedView(e,t){return this.createEmbeddedViewImpl(e,t)}createEmbeddedViewImpl(e,t,n){return new Kf(pf(this._declarationLView,this._declarationTContainer,e,{embeddedViewInjector:t,dehydratedView:n}))}}return e})();function Jf(){return Yf(Ho(),Bo())}function Yf(e,t){return e.type&4?new qf(t,e,jl(e,t)):null}function Xf(e,t,n,r,i){let a=e.data[t];if(a===null)a=Zf(e,t,n,r,i),Xee()&&(a.flags|=32);else if(a.type&64){a.type=n,a.value=r,a.attrs=i;let e=Jee();a.injectorIndex=e===null?-1:e.injectorIndex}return Wo(a,!0),a}function Zf(e,t,n,r,i){let a=Uo(),o=Go(),s=o?a:a&&a.parent,c=e.data[t]=$f(e,s,n,t,r,i);return Qf(e,c,a,o),c}function Qf(e,t,n,r){e.firstChild===null&&(e.firstChild=t),n!==null&&(r?n.child==null&&t.parent!==null&&(n.child=t):n.next===null&&(n.next=t,t.prev=n))}function $f(e,t,n,r,i,a){let o=t?t.injectorIndex:-1,s=0;return Lo()&&(s|=128),{type:n,index:r,insertBeforeIndex:null,injectorIndex:o,directiveStart:-1,directiveEnd:-1,directiveStylingLast:-1,componentOffset:-1,controlDirectiveIndex:-1,customControlIndex:-1,propertyBindings:null,flags:s,providerIndexes:0,value:i,namespace:ms(),attrs:a,mergedAttrs:null,localNames:null,initialInputs:null,inputs:null,hostDirectiveInputs:null,outputs:null,hostDirectiveOutputs:null,directiveToIndex:null,tView:null,next:null,prev:null,projectionNext:null,child:null,parent:t,projection:null,styles:null,stylesWithoutHost:null,residualStyles:void 0,classes:null,classesWithoutHost:null,residualClasses:void 0,classBindings:0,styleBindings:0}}function ep(e){let t=e[6]??[],n=e[3][11],r=[];for(let e of t)e.data.di===void 0?tp(e,n):r.push(e);e[6]=r}function tp(e,t){let n=0,r=e.firstChild;if(r){let i=e.data.r;for(;nnull,rp=()=>null;function ip(e,t){return np(e,t)}function ap(e,t,n){return rp(e,t,n)}var op=class{},sp=class{},cp=class{destroyNode=null;static __NG_ELEMENT_ID__=()=>lp()};function lp(){let e=Bo(),t=bo(Ho().index,e);return(ro(t)?t:e)[11]}var up=(()=>{class e{static ɵprov=xi({token:e,providedIn:`root`,factory:()=>null})}return e})();function dp(e){return e.debugInfo?.className||e.type.name||null}var fp={},pp=class{injector;parentInjector;constructor(e,t){this.injector=e,this.parentInjector=t}get(e,t,n){let r=this.injector.get(e,fp,n);return r!==fp||t===fp?r:this.parentInjector.get(e,t,n)}};function mp(e,t,n){return e[t]=n}function hp(e,t){return e[t]}function gp(e,t,n){if(n===Fu)return!1;let r=e[t];return!Object.is(r,n)&&(e[t]=n,!0)}function _p(e,t,n,r){let i=gp(e,t,n);return gp(e,t+1,r)||i}function vp(e,t,n,r,i){let a=_p(e,t,n,r);return gp(e,t+2,i)||a}function yp(e,t,n,r,i,a){let o=_p(e,t,n,r);return _p(e,t+2,i,a)||o}function bp(e,t,n){return function r(i){let a=r.__ngNativeEl__;a!==void 0&&eu(i,a),Rf(oo(e)?bo(e.index,t):t,5);let o=t[8],s=xp(t,o,n,i),c=r.__ngNextListenerFn__;for(;c;)s=xp(t,o,c,i)&&s,c=c.__ngNextListenerFn__;return s}}function xp(e,t,n,r){let i=an(null);try{return Nc(bc.OutputStart,t,n),n(r)!==!1}catch(t){return of(e,t),!1}finally{Nc(bc.OutputEnd,t,n),an(i)}}function Sp(e,t,n,r,i,a,o,s){let c=so(e),l=!1,u=null;if(!r&&c&&(u=wp(t,n,a,e.index)),u!==null){let e=u.__ngLastListenerFn__||u;e.__ngNextListenerFn__=o,u.__ngLastListenerFn__=o,l=!0}else{let o=go(e,n),c=r?r(o):o;r||(s.__ngNativeEl__=o);let l=i.listen(c,a,s);Cp(a)||Tp(r?t=>r(mo(t[e.index])):e.index,t,n,a,s,l,!1)}return l}function Cp(e){return e.startsWith(`animation`)||e.startsWith(`transition`)}function wp(e,t,n,r){let i=e.cleanup;if(i!=null)for(let e=0;er?n[r]:null}typeof a==`string`&&(e+=2)}return null}function Tp(e,t,n,r,i,a,o){let s=t.firstCreatePass?Mo(t):null,c=jo(n),l=c.length;c.push(i,a),s&&s.push(r,e,l,(l+1)*(o?-1:1))}function Ep(e,t,n,r,i){let a=null,o=null,s=null,c=!1,l=e.directiveToIndex.get(n.type);if(typeof l==`number`?a=l:[a,o,s]=l,o!==null&&s!==null&&e.hostDirectiveOutputs&&Object.hasOwn(e.hostDirectiveOutputs,r)){let n=e.hostDirectiveOutputs[r];for(let a=0;a=o&&l<=s)c=!0,Dp(e,t,l,n[a+1],r,i);else if(l>s)break}}return Object.hasOwn(n.outputs,r)&&(c=!0,Dp(e,t,a,r,r,i)),c}function Dp(e,t,n,r,i,a){let o=t[n],s=t[1],c=o[s.data[n].outputs[r]].subscribe(a);Tp(e.index,s,t,i,a,c,!0)}function Op(){kp()}function kp(){let e=Bo(),t=Vo(),n=Ho();if(t.firstCreatePass&&Fp(t,n),n.controlDirectiveIndex===-1)return;rd(`NgSignalForms`);let r=e[n.controlDirectiveIndex];t.data[n.controlDirectiveIndex].controlDef.create(r,new Mp(e,t,n))}function Ap(){jp()}function jp(){let e=Bo(),t=Vo(),n=ds();if(n.controlDirectiveIndex===-1)return;let r=t.data[n.controlDirectiveIndex].controlDef,i=e[n.controlDirectiveIndex];r.update(i,new Mp(e,t,n))}var Mp=class{lView;tView;tNode;hasPassThrough;constructor(e,t,n){this.lView=e,this.tView=t,this.tNode=n,this.hasPassThrough=!!(n.flags&4096)}get customControl(){return this.tNode.customControlIndex===-1?void 0:this.lView[this.tNode.customControlIndex]}get nativeElement(){return go(this.tNode,this.lView)}get descriptor(){return`<${this.tNode.value}>`}listenToCustomControlOutput(e,t){let n=this.tView.data[this.tNode.customControlIndex];Ep(this.tNode,this.lView,n,e,bp(this.tNode,this.lView,t))}listenToCustomControlModel(e){let t=this.tNode.flags&1024?`valueChange`:`checkedChange`,n=this.tView.data[this.tNode.customControlIndex];Ep(this.tNode,this.lView,n,t,bp(this.tNode,this.lView,e))}listenToDom(e,t){Sp(this.tNode,this.tView,this.lView,void 0,this.lView[11],e,t,bp(this.tNode,this.lView,t))}setInputOnDirectives(e,t,n){let r=this.tNode.inputs?.[e],i=this.tNode.hostDirectiveInputs?.[e];if(!r&&!i)return!1;let a=!1;if(r)for(let i of r){if(i===this.tNode.controlDirectiveIndex)continue;let r=this.lView[i],o=this.tView.data[i];(!n||n(Pp(r,o,e)))&&(Vd(o,r,e,t),a=!0)}if(i)for(let r=0;r0;){let e=n.shift();if(typeof e!=`function`){for(let n in e.inputs)t[e.inputs[n]]=!0;let r=Np(e.directive);r!==null&&n.push(...r);continue}for(let r of e()){if(typeof r==`function`)continue;if(r.inputs)for(let e=0;e1){t.flags|=4096;return}Ip(e,t)}function Ip(e,t){for(let n=t.directiveStart;n{let r=t.hostDirectiveInputs[e],i=t.hostDirectiveOutputs[e+`Change`];if(!r||!i)return!1;for(let e=0;e=i&&a<=o)return t.flags|=n,t.customControlIndex=r,!0}}return!1};if(e(`value`,1024)||e(`checked`,2048))return}}function Lp(e,t){return Rp(e,t)&&zp(e,t+`Change`)}function Rp(e,t){return t in e.inputs}function zp(e,t){return t in e.outputs}var Bp=Symbol(`BINDING`),Vp=new Oi(``);function Hp(e,t,n){let r=n?e.styles:null,i=n?e.classes:null,a=0;if(t!==null)for(let e=0;e0&&(n.directiveToIndex=new Map);for(let c=0;c0;){let n=e[--t];if(typeof n==`number`&&n<0)return n}return 0}function nm(e,t,n){if(n){if(t.exportAs)for(let r=0;r{let[n,r,i]=e[t],a={propName:n,templateName:t,isSignal:(r&Bd.SignalBased)!==0};return i&&(a.transform=i),a})}function um(e){return Object.keys(e).map(t=>({propName:e[t],templateName:t}))}function dm(e,t,n){let r=t instanceof Ha?t:t?.injector;return r&&e.getStandaloneInjector!==null&&(r=e.getStandaloneInjector(r)||r),r?new pp(n,r):n}function fm(e){let t=e.get(sp,null);if(t===null)throw new pi(407,!1);return{rendererFactory:t,sanitizer:e.get(up,null),changeDetectionScheduler:e.get(rc,null),ngReflect:!1,tracingService:e.get(td,null,{optional:!0})}}function pm(e,t,n){let r=hm(e);return xu(t,r,r===`svg`?`svg`:r===`math`?po:n)}function mm(e){if((e&&`localName`in e&&typeof e.localName==`string`?e.localName:e?.tagName)?.toLowerCase()===`script`)throw new pi(905,!1)}function hm(e){return(e.selectors[0][0]||`div`).toLowerCase()}var gm=class{componentDef;ngModule;selector;componentType;ngContentSelectors;isBoundToModule;cachedInputs=null;cachedOutputs=null;get inputs(){return this.cachedInputs??=lm(this.componentDef.inputs),this.cachedInputs}get outputs(){return this.cachedOutputs??=um(this.componentDef.outputs),this.cachedOutputs}constructor(e,t){this.componentDef=e,this.ngModule=t,this.componentType=e.type,this.selector=gte(e.selectors),this.ngContentSelectors=e.ngContentSelectors??[],this.isBoundToModule=!!t}create(e,t,n,r,i,a,o){Nc(bc.DynamicComponentStart);let s=an(null);try{let s=this.componentDef,c=dm(s,r||this.ngModule,e),l=fm(c),u=l.tracingService;return u&&u.componentCreate?u.componentCreate(dp(s),()=>this.createComponentRef(l,c,t,n,i,a,o)):this.createComponentRef(l,c,t,n,i,a,o)}finally{an(s)}}createComponentRef(e,t,n,r,i,a,o){let s=this.componentDef,c=_m(r,s,a,i),l=e.rendererFactory.createRenderer(null,s),u=r?Gd(l,r,s.encapsulation,t):pm(s,l,o??null);mm(u);let d=t.get(Vp,null),f=vm(u,()=>t.get(xs,null)??Xl());d&&d.addHost(f);let p=a?.some(bm)||i?.some(e=>typeof e!=`function`&&e.bindings.some(bm)),m=Pd(null,c,null,512|Id(s),null,null,e,l,t,null,ru(u,t,!0));d&&sm&&f instanceof ShadowRoot&&ko(m,()=>{d.removeHost(f)}),m[27]=u,rs(m);let ee=null;try{let e=im(27,m,2,`#host`,()=>c.directiveRegistry,!0,0);Eu(l,u,e),Wl(u,m),Ud(c,m,e),ou(c,e,m),am(c,e),n!==void 0&&Sm(e,this.ngContentSelectors,n),ee=bo(e.index,m),m[8]=ee[8],df(c,m,null)}catch(e){throw ee!==null&&Hl(ee),Hl(m),e}finally{Nc(bc.DynamicComponentEnd),cs()}return new xm(this.componentType,m,!!p)}};function _m(e,t,n,r){let i=e?[`ng-version`,`22.1.7`]:_te(t.selectors[0]),a=null,o=null,s=0;if(n)for(let e of n)s+=e[Bp].requiredVars,e.create&&(e.targetIdx=0,(a??=[]).push(e)),e.update&&(e.targetIdx=0,(o??=[]).push(e));if(r)for(let e=0;e{if(n&1&&e)for(let t of e)t.create();if(n&2&&t)for(let e of t)e.update()}}function bm(e){let t=e[Bp].kind;return t===`input`||t===`twoWay`}var xm=class extends op{_rootLView;_hasInputBindings;instance;hostView;changeDetectorRef;componentType;location;previousInputValues=null;_tNode;constructor(e,t,n){super(),this._rootLView=t,this._hasInputBindings=n,this._tNode=_o(t[1],27),this.location=jl(this._tNode,t),this.instance=bo(this._tNode.index,t)[8],this.hostView=this.changeDetectorRef=new Kf(t,void 0),this.componentType=e}setInput(e,t){this._hasInputBindings;let n=this._tNode;if(this.previousInputValues??=new Map,this.previousInputValues.has(e)&&Object.is(this.previousInputValues.get(e),t))return;let r=this._rootLView;sf(n,r[1],r,e,t),this.previousInputValues.set(e,t),Rf(bo(n.index,r),1)}get injector(){return new Cl(this._tNode,this._rootLView)}destroy(){this.hostView.destroy()}onDestroy(e){this.hostView.onDestroy(e)}};function Sm(e,t,n){let r=e.projection=[];for(let e=0;e{class e{static __NG_ELEMENT_ID__=wm}return e})();function wm(){return Om(Ho(),Bo())}var Tm=class e extends Cm{_lContainer;_hostTNode;_hostLView;constructor(e,t,n){super(),this._lContainer=e,this._hostTNode=t,this._hostLView=n}get element(){return jl(this._hostTNode,this._hostLView)}get injector(){return new Cl(this._hostTNode,this._hostLView)}get parentInjector(){let e=dl(this._hostTNode,this._hostLView);if(Zc(e)){let t=el(e,this._hostLView),n=Qc(e),r=t[1].data[n+8];return new Cl(r,t)}return new Cl(null,this._hostLView)}clear(){for(;this.length>0;)this.remove(this.length-1)}get(e){let t=Em(this._lContainer);return t!==null&&t[e]||null}get length(){return this._lContainer.length-10}createEmbeddedView(e,t,n){let r,i;typeof n==`number`?r=n:n!=null&&(r=n.index,i=n.injector);let a=ip(this._lContainer,e.ssrId),o=e.createEmbeddedViewImpl(t||{},i,a);return this.insertImpl(o,r,mf(this._hostTNode,a)),o}createComponent(e,t,n,r,i,a,o){let s,c=t||{};s=c.index,n=c.injector,r=c.projectableNodes,i=c.environmentInjector||c.ngModuleRef,a=c.directives,o=c.bindings;let l=new gm(Ii(e)),u=n||this.parentInjector;if(!i&&l.ngModule==null){let e=this.parentInjector.get(Ha,null);e&&(i=e)}let d=Ii(l.componentType??{}),f=ip(this._lContainer,d?.id??null),p=f?.firstChild??null,m=l.create(u,r,p,i,a,o,this._getHostElementNamespace());return this.insertImpl(m.hostView,s,mf(this._hostTNode,f)),m}_getHostElementNamespace(){if(this._hostTNode.type&2){let e=this._hostTNode.parent??this._hostLView[5];return e!==null&&e.type&2&&typeof e.value==`string`&&e.value.toLowerCase()===`foreignobject`?null:e?.namespace??null}return this._hostTNode.namespace}insert(e,t){return this.insertImpl(e,t,!0)}insertImpl(t,n,r){let i=t._lView;if(So(i)){let n=this.indexOf(t);if(n!==-1)this.detach(n);else{let n=i[3],r=new e(n,n[5],n[3]);r.detach(r.indexOf(t))}}let a=this._adjustIndex(n),o=this._lContainer;return Vf(o,i,a,r),t.attachToViewContainerRef(),fa(Dm(o),a,t),t}move(e,t){return this.insert(e,t)}indexOf(e){let t=Em(this._lContainer);return t===null?-1:t.indexOf(e)}remove(e){let t=this._adjustIndex(e,-1),n=Uf(this._lContainer,t);n&&(pa(Dm(this._lContainer),t),vd(n[1],n))}detach(e){let t=this._adjustIndex(e,-1),n=Uf(this._lContainer,t);return n&&pa(Dm(this._lContainer),t)!=null?new Kf(n):null}_adjustIndex(e,t=0){return e??this.length+t}};function Em(e){return e[8]}function Dm(e){return e[8]||=[]}function Om(e,t){let n,r=t[e.index];return io(r)?n=r:(n=zf(r,t,null,e),t[e.index]=n,Rd(t,n)),Am(n,t,e,r),new Tm(n,e,t)}function km(e,t){let n=e[11],r=n.createComment(``),i=go(t,e);return Su(n,n.parentNode(i),r,n.nextSibling(i),!1),r}var Am=Nm,jm=()=>!1;function Mm(e,t,n){return jm(e,t,n)}function Nm(e,t,n,r){if(e[7])return;let i;i=n.type&8?mo(r):km(t,n),e[7]=i}var Pm=class e{queryList;matches=null;constructor(e){this.queryList=e}clone(){return new e(this.queryList)}setDirty(){this.queryList.setDirty()}},Fm=class e{queries;constructor(e=[]){this.queries=e}createEmbeddedView(t){let n=t.queries;if(n!==null){let r=t.contentQueries===null?n.length:t.contentQueries[0],i=[];for(let e=0;e0)r.push(o[e/2]);else{let i=a[e+1],o=t[-n];for(let e=10;ee.trim())}function Ym(e,t,n){e.queries===null&&(e.queries=new Lm),e.queries.track(new Rm(t,n))}function Xm(e,t){return e.queries.getByIndex(t)}function Zm(e,t){let n=e[1],r=Xm(n,t);return r.crossesNgTemplate?Wm(n,e,t,[]):Um(n,e,r,t)}function Qm(e,t,n){let r,i=En(()=>{r._dirtyCounter();let n=nh(r,e);if(t&&n===void 0)throw new pi(-951,!1);return n});return r=i[rn],r._dirtyCounter=T(0),r._flatValue=void 0,i}function $m(e){return Qm(!0,!1,e)}function eh(e){return Qm(!0,!0,e)}function th(e,t){let n=e[rn];n._lView=Bo(),n._queryIndex=t,n._queryList=Gm(n._lView,t),n._queryList.onDirty(()=>n._dirtyCounter.update(e=>e+1))}function nh(e,t){let n=e._lView,r=e._queryIndex;if(n===void 0||r===void 0||n[2]&4)return t?void 0:xa;let i=Gm(n,r),a=Zm(n,r);return i.reset(a,Nl),t?i.first:i._changesDetected||e._flatValue===void 0?e._flatValue=i.toArray():e._flatValue}function rh(e){return!!e&&typeof e.then==`function`}function ih(e){return!!e&&typeof e.subscribe==`function`}var ah=class{},oh=class extends ah{injector;instance=null;constructor(e){super();let t=new Ua([...e.providers,{provide:ah,useValue:this}],e.parent||Va(),e.debugName,new Set([`environment`]));this.injector=t,e.runEnvironmentInitializers&&t.resolveInjectorInitializers()}destroy(){this.injector.destroy()}onDestroy(e){this.injector.onDestroy(e)}};function sh(e,t,n=null){return new oh({providers:e,parent:t,debugName:n,runEnvironmentInitializers:!0}).injector}var ch=(()=>{class e{_injector;cachedInjectors=new Map;constructor(e){this._injector=e}getOrCreateStandaloneInjector(e){if(!e.standalone)return null;if(!this.cachedInjectors.has(e)){let t=Da(!1,e.type),n=t.length>0?sh([t],this._injector,``):null;this.cachedInjectors.set(e,n)}return this.cachedInjectors.get(e)}ngOnDestroy(){try{for(let e of this.cachedInjectors.values())e!==null&&e.destroy()}finally{this.cachedInjectors.clear()}}static ɵprov=xi({token:e,providedIn:`environment`,factory:()=>new e(ra(Ha))})}return e})();function O(e){return yc(()=>{let t=mh(e),n={...t,decls:e.decls,vars:e.vars,template:e.template,consts:e.consts||null,ngContentSelectors:e.ngContentSelectors,onPush:e.changeDetection!==Ll.Eager,directiveDefs:null,pipeDefs:null,dependencies:t.standalone&&e.dependencies||null,getStandaloneInjector:t.standalone?e=>e.get(ch).getOrCreateStandaloneInjector(n):null,getExternalStyles:null,signals:e.signals??!1,data:e.data||{},encapsulation:e.encapsulation||su.Emulated,styles:e.styles||xa,_:null,schemas:e.schemas||null,tView:null,id:``};t.standalone&&rd(`NgStandalone`),hh(n);let r=e.dependencies;return n.directiveDefs=gh(r,lh),n.pipeDefs=gh(r,Ri),n.id=_h(n),n})}function lh(e){return Ii(e)||Li(e)}function uh(e,t){if(e==null)return ba;let n={};for(let r in e)if(Object.hasOwn(e,r)){let i=e[r],a,o,s,c;Array.isArray(i)?(s=i[0],a=i[1],o=i[2]??a,c=i[3]||null):(a=i,o=i,s=Bd.None,c=null),n[a]=[r,s,c],t[a]=o}return n}function dh(e){if(e==null)return ba;let t={};for(let n in e)Object.hasOwn(e,n)&&(t[e[n]]=n);return t}function fh(e){return yc(()=>{let t=mh(e);return hh(t),t})}function ph(e){return{type:e.type,name:e.name,factory:null,pure:e.pure!==!1,standalone:e.standalone??!0,onDestroy:e.type.prototype.ngOnDestroy||null}}function mh(e){let t={};return{type:e.type,providersResolver:null,viewProvidersResolver:null,factory:null,hostBindings:e.hostBindings||null,hostVars:e.hostVars||0,hostAttrs:e.hostAttrs||null,contentQueries:e.contentQueries||null,declaredInputs:t,inputConfig:e.inputs||ba,exportAs:e.exportAs||null,standalone:e.standalone??!0,signals:e.signals===!0,selectors:e.selectors||xa,viewQuery:e.viewQuery||null,features:e.features||null,setInput:null,resolveHostDirectives:null,hostDirectives:null,controlDef:null,signalFormsInputPresence:null,inputs:uh(e.inputs,t),outputs:dh(e.outputs),debugInfo:null}}function hh(e){e.features?.forEach(t=>t(e))}function gh(e,t){return e?()=>{let n=typeof e==`function`?e():e,r=[];for(let e of n){let n=t(e);n!==null&&r.push(n)}return r}:null}function _h(e){let t=0,n=typeof e.consts==`function`?``:e.consts,r=[e.selectors,e.ngContentSelectors,e.hostVars,e.hostAttrs,n,e.vars,e.decls,e.encapsulation,e.standalone,e.signals,e.exportAs,JSON.stringify(e.inputs),JSON.stringify(e.outputs),Object.getOwnPropertyNames(e.type.prototype),!!e.contentQueries,!!e.viewQuery];for(let e of r.join(`|`))t=Math.imul(31,t)+e.charCodeAt(0)<<0;return t+=2147483648,`c`+t}var vh=new Oi(``),yh=(()=>{class e{resolve;reject;initialized=!1;done=!1;donePromise=new Promise((e,t)=>{this.resolve=e,this.reject=t});appInits=x(vh,{optional:!0})??[];injector=x(bs);constructor(){}runInitializers(){if(this.initialized)return;let e=[];for(let t of this.appInits){let n=eo(this.injector,t);if(rh(n))e.push(n);else if(ih(n)){let t=new Promise((e,t)=>{n.subscribe({complete:e,error:t})});e.push(t)}}let t=()=>{this.done=!0,this.resolve()};Promise.all(e).then(()=>{t()}).catch(e=>{this.reject(e)}),e.length===0&&t(),this.initialized=!0}static ɵfac=function(t){return new(t||e)};static ɵprov=kl({token:e,factory:e.ɵfac})}return e})();function bh(e){return t=>{t.controlDef={create:(e,t)=>{e?.ɵngControlCreate(t)},update:(e,t)=>{e?.ɵngControlUpdate?.(t)},passThroughInput:e}}}function xh(e){return Object.getPrototypeOf(e.prototype).constructor}function Sh(e){let t=xh(e.type),n=!0,r=[e];for(;t&&t!==Function.prototype&&t!==Object.prototype;){let i,a=Object.hasOwn(t,Ai)?t[Ai]:void 0,o=Object.hasOwn(t,ji)?t[ji]:void 0;if(co(e))i=a??o;else{if(a)throw new pi(903,!1);i=o}if(i){if(n){r.push(i);let t=e;t.inputs=Th(e.inputs),t.declaredInputs=Th(e.declaredInputs),t.outputs=Th(e.outputs);let n=i.hostBindings;n&&Oh(e,n);let a=i.viewQuery,o=i.contentQueries;if(a&&Eh(e,a),o&&Dh(e,o),Ch(e,i),Vee(e.outputs,i.outputs),co(i)&&i.data.animation){let t=e.data;t.animation=(t.animation||[]).concat(i.data.animation)}}let t=i.features;if(t)for(let r=0;r=0;r--){let i=e[r];i.hostVars=t+=i.hostVars,i.hostAttrs=Yc(i.hostAttrs,n=Yc(n,i.hostAttrs))}}function Th(e){return e===ba?{}:e===xa?[]:e}function Eh(e,t){let n=e.viewQuery;e.viewQuery=n?(e,r)=>{t(e,r),n(e,r)}:t}function Dh(e,t){let n=e.contentQueries;e.contentQueries=n?(e,r,i)=>{t(e,r,i),n(e,r,i)}:t}function Oh(e,t){let n=e.hostBindings;e.hostBindings=n?(e,r)=>{t(e,r),n(e,r)}:t}function kh(e,t,n,r,i,a,o,s){if(n.firstCreatePass){e.mergedAttrs=Yc(e.mergedAttrs,e.attrs);let t=e.tView=jd(2,e,i,a,o,n.directiveRegistry,n.pipeRegistry,null,n.schemas,n.consts,null);n.queries!==null&&(n.queries.template(n,e),t.queries=n.queries.embeddedTView(e))}s&&(e.flags|=s),Wo(e,!1);let c=Nh(n,t,e,r);gs()&&Sd(n,t,c,e),Wl(c,t);let l=zf(c,t,c,e);t[r+27]=l,Rd(t,l),Mm(l,e,t)}function Ah(e,t,n,r,i,a,o,s,c,l,u){let d=n+27,f;return t.firstCreatePass?(f=Xf(t,d,4,o||null,s||null),Io()&&Wp(t,e,f,Co(t.consts,l),$d),Fc(t,f)):f=t.data[d],kh(f,e,t,n,r,i,a,c),so(f)&&Ud(t,e,f),l!=null&&Wd(e,f,u),f}function jh(e,t,n,r,i,a,o,s,c,l,u){let d=n+27,f;if(t.firstCreatePass){if(f=Xf(t,d,4,o||null,s||null),l!=null){let e=Co(t.consts,l);f.localNames=[];for(let t=0;t{throw new pi(600,``)})}var Rh=10,zh=(()=>{class e{_runningTick=!1;_destroyed=!1;_destroyListeners=[];_views=[];internalErrorHandler=x(qs);afterRenderManager=x(id);zonelessEnabled=x(ic);rootEffectScheduler=x(oc);dirtyFlags=0;tracingSnapshot=null;allTestViews=new Set;autoDetectTestViews=new Set;includeAllTestViews=!1;afterTick=new Wr;get allViews(){return[...(this.includeAllTestViews?this.allTestViews:this.autoDetectTestViews).keys(),...this._views]}get destroyed(){return this._destroyed}componentTypes=[];components=[];internalPendingTask=x(Es);get isStable(){return this.internalPendingTask.hasPendingTasksObservable.pipe(di(e=>!e))}constructor(){x(td,{optional:!0})}whenStable(){let e;return new Promise(t=>{e=this.isStable.subscribe({next:e=>{e&&t()}})}).finally(()=>{e.unsubscribe()})}_injector=x(Ha);_rendererFactory=null;get injector(){return this._injector}bootstrap(e,t){return this.bootstrapImpl(e,t)}bootstrapImpl(e,t,n=bs.NULL){return this._injector.get(Ps).run(()=>{if(Nc(bc.BootstrapComponentStart),!this._injector.get(yh).done)throw new pi(405,``);let r=Ii(e),i=this._injector.get(ah),a=new gm(r,i);this.componentTypes.push(e);let{hostElement:o,directives:s,bindings:c}=Bh(t),l=o||a.selector,u=a.create(n,[],l,i.injector,s,c),d=u.location.nativeElement,f=u.injector.get(Fh,null);return f?.registerApplication(d),u.onDestroy(()=>{this.detachView(u.hostView),Vh(this.components,u),f?.unregisterApplication(d)}),this._loadComponent(u),Nc(bc.BootstrapComponentEnd,u),u})}tick(){this.zonelessEnabled||(this.dirtyFlags|=1),this._tick()}_tick(){Nc(bc.ChangeDetectionStart),this.tracingSnapshot===null?this.tickImpl():this.tracingSnapshot.run(ed.CHANGE_DETECTION,this.tickImpl)}tickImpl=()=>{if(this._runningTick)throw Nc(bc.ChangeDetectionEnd),new pi(101,!1);let e=an(null);try{this._runningTick=!0,this.synchronize()}finally{this._runningTick=!1,this.tracingSnapshot?.dispose(),this.tracingSnapshot=null,an(e),this.afterTick.next(),Nc(bc.ChangeDetectionEnd)}};synchronize(){this._rendererFactory===null&&!this._injector.destroyed&&(this._rendererFactory=this._injector.get(sp,null,{optional:!0}));let e=0;for(;this.dirtyFlags!==0&&e++Eo(e))){this.dirtyFlags|=2;return}this.dirtyFlags&=-8}attachView(e){let t=e;this._views.push(t),t.attachToAppRef(this)}detachView(e){let t=e;Vh(this._views,t),t.detachFromAppRef()}_loadComponent(e){this.attachView(e.hostView);try{this.tick()}catch(e){this.internalErrorHandler(e)}this.components.push(e),this._injector.get(Ih,[]).forEach(t=>t(e))}ngOnDestroy(){if(!this._destroyed)try{this._destroyListeners.forEach(e=>e()),this._views.slice().forEach(e=>e.destroy())}finally{this._destroyed=!0,this._views=[],this._destroyListeners=[]}}onDestroy(e){return this._destroyListeners.push(e),()=>Vh(this._destroyListeners,e)}destroy(){if(this._destroyed)throw new pi(406,!1);let e=this._injector;e.destroy&&!e.destroyed&&e.destroy()}get viewCount(){return this._views.length}static ɵfac=function(t){return new(t||e)};static ɵprov=kl({token:e,factory:e.ɵfac})}return e})();function Bh(e){return e===void 0||typeof e==`string`||e instanceof Element?{hostElement:e}:e}function Vh(e,t){let n=e.indexOf(t);n>-1&&e.splice(n,1)}function k(e,t,n,r){let i=Bo();return gp(i,Qo(),t)&&(Vo(),ef(ds(),i,e,t,n,r)),k}function Hh(e){if(rd(`NgAnimateEnter`),!Wu)return Hh;let t=Bo();if(Dte(t))return Hh;let n=Ho(),r=t[9].get(Ps);return Mte(Zu(t),n,()=>Uh(t,n,e,r)),Rte(t[9]),dd(t[9],Zu(t)),Hh}function Uh(e,t,n,r){let i=go(t,e),a=e[11],o=jte(n),s=[],c=!1,l=e=>{if(Qu(e)!==i)return;let t=e instanceof AnimationEvent?`animationend`:`transitionend`;r.runOutsideAngular(()=>{a.listen(i,t,u)})},u=e=>{Qu(e)===i&&($u(e,i)&&(c=!0),Wh(e,i,a))};if(o&&o.length>0){r.runOutsideAngular(()=>{s.push(a.listen(i,`animationstart`,l)),s.push(a.listen(i,`transitionstart`,l))}),Ote(i,o,s);for(let e of o)a.addClass(i,e);r.runOutsideAngular(()=>{requestAnimationFrame(()=>{if(!c&&(Cte(i,qu,Wu),!qu.has(i))){for(let e of o)a.removeClass(i,e);Gu(i)}})})}}function Wh(e,t,n){let r=Ku.get(t);if(Qu(e)===t&&r&&$u(e,t)){e.stopPropagation();for(let e of r.classList)n.removeClass(t,e);Gu(t)}}var Gh=class{destroy(e){}updateValue(e,t){}swap(e,t){let n=Math.min(e,t),r=Math.max(e,t),i=this.detach(r);if(r-n>1){let e=this.detach(n);this.attach(n,i),this.attach(r,e)}else this.attach(n,i)}move(e,t){this.attach(t,this.detach(e))}};function Kh(e,t,n,r,i){return e===n&&Object.is(t,r)?1:Object.is(i(e,t),i(n,r))?-1:0}function qh(e,t,n,r){let i,a,o=0,s=e.length-1;if(Array.isArray(t)){an(r);let c=t.length-1;for(an(null);o<=s&&o<=c;){let r=e.at(o),l=t[o],u=Kh(o,r,o,l,n);if(u!==0){u<0&&e.updateValue(o,l),o++;continue}let d=e.at(s),f=t[c],p=Kh(s,d,c,f,n);if(p!==0){p<0&&e.updateValue(s,f),s--,c--;continue}let m=n(o,r),ee=n(s,d),h=n(o,l);if(Object.is(h,ee)){let t=n(c,f);Object.is(t,m)?(e.swap(o,s),e.updateValue(s,f),c--,s--):e.move(s,o),e.updateValue(o,l),o++;continue}if(i??=new Zh,a??=Xh(e,o,s,n),Jh(e,i,o,h))e.updateValue(o,l),o++,s++;else if(a.has(h))i.set(m,e.detach(o)),s--;else{let n=e.create(o,t[o]);e.attach(o,n),o++,s++}}for(;o<=c;)Yh(e,i,n,o,t[o]),o++}else if(t!=null){an(r);let c=t[Symbol.iterator]();an(null);let l=c.next();for(;!l.done&&o<=s;){let t=e.at(o),r=l.value,u=Kh(o,t,o,r,n);if(u!==0)u<0&&e.updateValue(o,r),o++,l=c.next();else{i??=new Zh,a??=Xh(e,o,s,n);let u=n(o,r);if(Jh(e,i,o,u))e.updateValue(o,r),o++,s++,l=c.next();else if(!a.has(u))e.attach(o,e.create(o,r)),o++,s++,l=c.next();else{let r=n(o,t);i.set(r,e.detach(o)),s--}}}for(;!l.done;)Yh(e,i,n,e.length,l.value),l=c.next()}for(;o<=s;)e.destroy(e.detach(s--));i?.forEach(t=>{e.destroy(t)})}function Jh(e,t,n,r){return t!==void 0&&t.has(r)?(e.attach(n,t.get(r)),t.delete(r),!0):!1}function Yh(e,t,n,r,i){if(Jh(e,t,r,n(r,i)))e.updateValue(r,i);else{let t=e.create(r,i);e.attach(r,t)}}function Xh(e,t,n,r){let i=new Set;for(let a=t;a<=n;a++)i.add(r(a,e.at(a)));return i}var Zh=class{kvMap=new Map;_vMap=void 0;has(e){return this.kvMap.has(e)}delete(e){if(!this.has(e))return!1;let t=this.kvMap.get(e);return this._vMap!==void 0&&this._vMap.has(t)?(this.kvMap.set(e,this._vMap.get(t)),this._vMap.delete(t)):this.kvMap.delete(e),!0}get(e){return this.kvMap.get(e)}set(e,t){if(this.kvMap.has(e)){let n=this.kvMap.get(e);this._vMap===void 0&&(this._vMap=new Map);let r=this._vMap;for(;r.has(n);)n=r.get(n);r.set(n,t)}else this.kvMap.set(e,t)}forEach(e){for(let[t,n]of this.kvMap)if(e(n,t),this._vMap!==void 0){let r=this._vMap;for(;r.has(n);)n=r.get(n),e(n,t)}}};function A(e,t,n,r,i,a,o,s){rd(`NgControlFlow`);let c=Bo(),l=Vo();return jh(c,l,e,t,n,r,i,Co(l.consts,a),256,o,s),Qh}function Qh(e,t,n,r,i,a,o,s){rd(`NgControlFlow`);let c=Bo(),l=Vo();return jh(c,l,e,t,n,r,i,Co(l.consts,a),512,o,s),Qh}function j(e,t){rd(`NgControlFlow`);let n=Bo(),r=Qo(),i=n[r]===Fu?-1:n[r],a=i===-1?void 0:ig(n,27+i);if(gp(n,r,e)){let r=an(null);try{if(a!==void 0&&Hf(a,0),e!==-1){let r=27+e,i=ig(n,r),a=lg(n[1],r),o=ap(i,a,n);Vf(i,pf(n,a,t,{dehydratedView:o}),0,mf(a,o))}}finally{an(r)}}else if(a!==void 0){let e=Bf(a,0);e!==void 0&&(e[8]=t)}}var $h=class{lContainer;$implicit;$index;constructor(e,t,n){this.lContainer=e,this.$implicit=t,this.$index=n}get $count(){return this.lContainer.length-10}};function eg(e){return e}function tg(e,t){return t}var ng=class{hasEmptyBlock;trackByFn;liveCollection;constructor(e,t,n){this.hasEmptyBlock=e,this.trackByFn=t,this.liveCollection=n}};function M(e,t,n,r,i,a,o,s,c,l,u,d,f){rd(`NgControlFlow`);let p=Bo(),m=Vo(),ee=c!==void 0,h=Bo(),g=new ng(ee,s?o.bind(h[15][8]):o);h[27+e]=g,jh(p,m,e+1,t,n,r,i,Co(m.consts,a),256),ee&&jh(p,m,e+2,c,l,u,d,Co(m.consts,f),512)}var rg=class extends Gh{lContainer;hostLView;templateTNode;operationsCounter=void 0;needsIndexUpdate=!1;constructor(e,t,n){super(),this.lContainer=e,this.hostLView=t,this.templateTNode=n}get length(){return this.lContainer.length-10}at(e){return this.getLView(e)[8].$implicit}attach(e,t){let n=t[6];this.needsIndexUpdate||=e!==this.length,Vf(this.lContainer,t,e,mf(this.templateTNode,n)),ag(this.lContainer,e)}detach(e){return this.needsIndexUpdate||=e!==this.length-1,og(this.lContainer,e),sg(this.lContainer,e)}create(e,t){let n=ip(this.lContainer,this.templateTNode.tView.ssrId);return pf(this.hostLView,this.templateTNode,new $h(this.lContainer,t,e),{dehydratedView:n})}destroy(e){vd(e[1],e)}updateValue(e,t){this.getLView(e)[8].$implicit=t}reset(){this.needsIndexUpdate=!1}updateIndexes(){if(this.needsIndexUpdate)for(let e=0;e0){let e=n[9];Ite(e,r),Uu.delete(n[19]),r.detachedLeaveAnimationFns=void 0}}function og(e,t){if(e.length<=10)return;let n=e[10+t],r=n?n[26]:void 0;r&&r.leave&&r.leave.size>0&&(r.detachedLeaveAnimationFns=[])}function sg(e,t){return Uf(e,t)}function cg(e,t){return Bf(e,t)}function lg(e,t){return _o(e,t)}function P(e,t,n){let r=Bo();return gp(r,Qo(),t)&&(Vo(),qd(ds(),r,e,t,r[11],n)),P}function ug(e,t,n,r,i){sf(t,e,n,i?`class`:`style`,r)}function F(e,t,n,r){let i=Bo(),a=i[1],o=e+27,s=a.firstCreatePass?im(o,i,2,t,$d,Io(),n,r):a.data[o];if(oo(s)){let n=i[10].tracingService;if(n&&n.componentCreate){let o=a.data[s.directiveStart+s.componentOffset];return n.componentCreate(dp(o),()=>(dg(e,t,i,s,r),F))}}return dg(e,t,i,s,r),F}function dg(e,t,n,r,i){if(rf(r,n,e,t,pg),so(r)){let e=n[1];Ud(e,n,r),ou(e,r,n)}i!=null&&Wd(n,r)}function I(){let e=Vo(),t=af(Ho());return e.firstCreatePass&&am(e,t),Ro(t)&&zo(),Fo(),t.classesWithoutHost!=null&&Wc(t)&&ug(e,t,Bo(),t.classesWithoutHost,!0),t.stylesWithoutHost!=null&&Gc(t)&&ug(e,t,Bo(),t.stylesWithoutHost,!1),I}function L(e,t,n,r){return F(e,t,n,r),I(),L}function R(e,t,n,r){let i=Bo(),a=i[1],o=e+27,s=a.firstCreatePass?om(o,a,2,t,n,r):a.data[o];return rf(s,i,e,t,pg),r!=null&&Wd(i,s),R}function z(){return Ro(af(Ho()))&&zo(),Fo(),z}function fg(e,t,n,r){return R(e,t,n,r),z(),fg}var pg=(e,t,n,r,i)=>(_s(!0),xu(t[11],r,ms()));function mg(e,t,n){let r=Bo(),i=r[1],a=e+27,o=i.firstCreatePass?im(a,r,8,`ng-container`,$d,Io(),t,n):i.data[a];if(rf(o,r,e,`ng-container`,_g),so(o)){let e=r[1];Ud(e,r,o),ou(e,o,r)}return n!=null&&Wd(r,o),mg}function hg(){let e=Vo(),t=af(Ho());return e.firstCreatePass&&am(e,t),hg}function gg(e,t,n){return mg(e,t,n),hg(),gg}var _g=(e,t,n,r,i)=>(_s(!0),bu(t[11],``));function B(){return Bo()}function V(e,t,n){let r=Bo();return gp(r,Qo(),t)&&(Vo(),Jd(ds(),r,e,t,r[11],n)),V}var vg=void 0;function yg(e){let t=Math.floor(Math.abs(e)),n=e.toString().replace(/^[^.]*\.?/,``).length;return t===1&&n===0?1:5}var bg=[`en`,[[`a`,`p`],[`AM`,`PM`]],[[`AM`,`PM`]],[[`S`,`M`,`T`,`W`,`T`,`F`,`S`],[`Sun`,`Mon`,`Tue`,`Wed`,`Thu`,`Fri`,`Sat`],[`Sunday`,`Monday`,`Tuesday`,`Wednesday`,`Thursday`,`Friday`,`Saturday`],[`Su`,`Mo`,`Tu`,`We`,`Th`,`Fr`,`Sa`]],vg,[[`J`,`F`,`M`,`A`,`M`,`J`,`J`,`A`,`S`,`O`,`N`,`D`],[`Jan`,`Feb`,`Mar`,`Apr`,`May`,`Jun`,`Jul`,`Aug`,`Sep`,`Oct`,`Nov`,`Dec`],[`January`,`February`,`March`,`April`,`May`,`June`,`July`,`August`,`September`,`October`,`November`,`December`]],vg,[[`B`,`A`],[`BC`,`AD`],[`Before Christ`,`Anno Domini`]],0,[6,0],[`M/d/yy`,`MMM d, y`,`MMMM d, y`,`EEEE, MMMM d, y`],[`h:mm a`,`h:mm:ss a`,`h:mm:ss a z`,`h:mm:ss a zzzz`],[`{1}, {0}`,vg,vg,vg],[`.`,`,`,`;`,`%`,`+`,`-`,`E`,`×`,`‰`,`∞`,`NaN`,`:`],[`#,##0.###`,`#,##0%`,`¤#,##0.00`,`#E0`],`USD`,`$`,`US Dollar`,{},`ltr`,yg],xg=Object.create(null);function Sg(e){let t=Tg(e),n=Cg(t);if(n)return n;let r=t.split(`-`)[0];if(n=Cg(r),n)return n;if(r===`en`)return bg;throw new pi(701,!1)}function Cg(e){if(!(e in xg)){let t=Qi.ng&&Qi.ng.common&&Qi.ng.common.locales&&Qi.ng.common.locales[e];return t!==void 0&&(xg[e]=t),t}return xg[e]}var wg={LocaleId:0,DayPeriodsFormat:1,DayPeriodsStandalone:2,DaysFormat:3,DaysStandalone:4,MonthsFormat:5,MonthsStandalone:6,Eras:7,FirstDayOfWeek:8,WeekendRange:9,DateFormat:10,TimeFormat:11,DateTimeFormat:12,NumberSymbols:13,NumberFormats:14,CurrencyCode:15,CurrencySymbol:16,CurrencyName:17,Currencies:18,Directionality:19,PluralCase:20,ExtraData:21};function Tg(e){return e.toLowerCase().replace(/_/g,`-`)}var Eg=`en-US`;function Dg(e){typeof e==`string`&&e.toLowerCase().replace(/_/g,`-`)}function H(e,t,n){let r=Bo(),i=Vo(),a=Ho();return Og(i,r,r[11],a,e,t,n),H}function U(e,t,n){let r=Bo(),i=Vo(),a=Ho();return(a.type&3||n)&&Sp(a,i,r,n,r[11],e,t,bp(a,r,t)),U}function Og(e,t,n,r,i,a,o){let s=!0,c=null;if((r.type&3||o)&&(c??=bp(r,t,a),Sp(r,e,t,o,n,i,a,c)&&(s=!1)),s){let e=r.outputs?.[i],n=r.hostDirectiveOutputs?.[i];if(n&&n.length)for(let e=0;e>17&32767}function Rg(e){return(e&2)==2}function zg(e,t){return e&131071|t<<17}function Bg(e){return e|2}function Vg(e){return(e&131068)>>2}function Hg(e,t){return e&-131069|t<<2}function Ug(e){return(e&1)==1}function Wg(e){return e|1}function Gg(e,t,n,r,i,a){let o=a?t.classBindings:t.styleBindings,s=Lg(o),c=Vg(o);e[r]=n;let l=!1,u;if(Array.isArray(n)){let e=n;u=e[1],(u===null||va(e,u)>0)&&(l=!0)}else u=n;if(i){if(c!==0){let t=Lg(e[s+1]);e[r+1]=Ig(t,s),t!==0&&(e[t+1]=Hg(e[t+1],r)),e[s+1]=zg(e[s+1],r)}else e[r+1]=Ig(s,0),s!==0&&(e[s+1]=Hg(e[s+1],r)),s=r}else e[r+1]=Ig(c,0),s===0?s=r:e[c+1]=Hg(e[c+1],r),c=r;l&&(e[r+1]=Bg(e[r+1])),qg(e,u,r,!0),qg(e,u,r,!1),Kg(t,u,e,r,a),o=Ig(s,c),a?t.classBindings=o:t.styleBindings=o}function Kg(e,t,n,r,i){let a=i?e.residualClasses:e.residualStyles;a!=null&&typeof t==`string`&&va(a,t)>=0&&(n[r+1]=Wg(n[r+1]))}function qg(e,t,n,r){let i=e[n+1],a=t===null,o=r?Lg(i):Vg(i),s=!1;for(;o!==0&&(s===!1||a);){let n=e[o],i=e[o+1];Jg(n,t)&&(s=!0,e[o+1]=r?Wg(i):Bg(i)),o=r?Lg(i):Vg(i)}s&&(e[n+1]=r?Bg(i):Wg(i))}function Jg(e,t){return e===null||t==null||(Array.isArray(e)?e[1]:e)===t?!0:Array.isArray(e)&&typeof t==`string`?va(e,t)>=0:!1}var Yg={textEnd:0,key:0,keyEnd:0,value:0,valueEnd:0};function Xg(e){return e.substring(Yg.key,Yg.keyEnd)}function Zg(e){return $g(e),Qg(e,e_(e,0,Yg.textEnd))}function Qg(e,t){let n=Yg.textEnd;return n===t?-1:(t=Yg.keyEnd=t_(e,Yg.key=t,n),e_(e,t,n))}function $g(e){Yg.key=0,Yg.keyEnd=0,Yg.value=0,Yg.valueEnd=0,Yg.textEnd=e.length}function e_(e,t,n){for(;t32;)t++;return t}function n_(e,t,n){return a_(e,t,n,!1),n_}function G(e,t){return a_(e,t,null,!0),G}function r_(e){o_(g_,i_,e,!0)}function i_(e,t){for(let n=Zg(t);n>=0;n=Qg(t,n))ga(e,Xg(t),!0)}function a_(e,t,n,r){let i=Bo(),a=Vo(),o=$o(2);if(a.firstUpdatePass&&c_(a,e,o,r),t!==Fu&&gp(i,o,t)){let s=a.data[ls()];v_(a,s,i,i[11],e,i[o+1]=x_(t,n),r,o)}}function o_(e,t,n,r){let i=Vo(),a=$o(2);i.firstUpdatePass&&c_(i,null,a,r);let o=Bo();if(n!==Fu&&gp(o,a,n)){let s=i.data[ls()];if(S_(s,r)&&!s_(i,a)){let e=r?s.classesWithoutHost:s.stylesWithoutHost;e!==null&&(n=_i(e,n||``)),ug(i,s,o,n,r)}else __(i,s,o,o[11],o[a+1],o[a+1]=h_(e,t,n),r,a)}}function s_(e,t){return t>=e.expandoStartIndex}function c_(e,t,n,r){let i=e.data;if(i[n+1]===null){let a=i[ls()],o=s_(e,n);S_(a,r)&&t===null&&!o&&(t=!1),t=l_(i,a,t,r),Gg(i,a,t,n,o,r)}}function l_(e,t,n,r){let i=$ee(e),a=r?t.residualClasses:t.residualStyles;if(i===null)(r?t.classBindings:t.styleBindings)===0&&(n=p_(null,e,t,n,r),n=m_(n,t.attrs,r),a=null);else{let o=t.directiveStylingLast;if(o===-1||e[o]!==i){if(n=p_(i,e,t,n,r),a===null){let n=u_(e,t,r);n!==void 0&&Array.isArray(n)&&(n=p_(null,e,t,n[1],r),n=m_(n,t.attrs,r),d_(e,t,r,n))}else a=f_(e,t,r)}}return a!==void 0&&(r?t.residualClasses=a:t.residualStyles=a),n}function u_(e,t,n){let r=n?t.classBindings:t.styleBindings;if(Vg(r)!==0)return e[Lg(r)]}function d_(e,t,n,r){let i=n?t.classBindings:t.styleBindings;e[Lg(i)]=r}function f_(e,t,n){let r,i=t.directiveEnd;for(let a=1+t.directiveStylingLast;a0;){let t=e[i],a=Array.isArray(t),c=a?t[1]:t,l=c===null,u=n[i+1];u===Fu&&(u=l?xa:void 0);let d=l?_a(u,r):c===r?u:void 0;if(a&&!b_(d)&&(d=_a(t,r)),b_(d)&&(s=d,o))return s;let f=e[i+1];i=o?Lg(f):Vg(f)}if(t!==null){let e=a?t.residualClasses:t.residualStyles;e!=null&&(s=_a(e,r))}return s}function b_(e){return e!==void 0}function x_(e,t){return e==null||e===``||(typeof t==`string`?e=lu(e)+t:typeof e==`object`&&(e=gi(lu(e)))),e}function S_(e,t){return!!(e.flags&(t?8:16))}function K(e,t=``){let n=Bo(),r=Vo(),i=e+27,a=r.firstCreatePass?Xf(r,i,1,t,null):r.data[i],o=C_(r,n,a,t);n[i]=o,gs()&&Sd(r,n,o,a),Wo(a,!1)}var C_=(e,t,n,r)=>(_s(!0),vu(t[11],r));function w_(e,t,n,r=``){return gp(e,Qo(),n)?t+Bi(n)+r:Fu}function T_(e,t,n,r,i,a=``){let o=_p(e,Zo(),n,i);return $o(2),o?t+Bi(n)+r+Bi(i)+a:Fu}function E_(e,t,n,r,i,a,o,s=``){let c=vp(e,Zo(),n,i,o);return $o(3),c?t+Bi(n)+r+Bi(i)+a+Bi(o)+s:Fu}function D_(e,t,n,r,i,a,o,s,c,l=``){let u=yp(e,Zo(),n,i,o,c);return $o(4),u?t+Bi(n)+r+Bi(i)+a+Bi(o)+s+Bi(c)+l:Fu}function q(e){return J(``,e),q}function J(e,t,n){let r=Bo(),i=w_(r,e,t,n);return i!==Fu&&A_(r,ls(),i),J}function Y(e,t,n,r,i){let a=Bo(),o=T_(a,e,t,n,r,i);return o!==Fu&&A_(a,ls(),o),Y}function O_(e,t,n,r,i,a,o){let s=Bo(),c=E_(s,e,t,n,r,i,a,o);return c!==Fu&&A_(s,ls(),c),O_}function k_(e,t,n,r,i,a,o,s,c){let l=Bo(),u=D_(l,e,t,n,r,i,a,o,s,c);return u!==Fu&&A_(l,ls(),u),k_}function A_(e,t,n){let r=ho(t,e);yu(e[11],r,n)}function j_(e,t,n){gc(t)&&(t=t());let r=Bo();return gp(r,Qo(),t)&&(Vo(),qd(ds(),r,e,t,r[11],n)),j_}function M_(e,t){let n=gc(e);return n&&e.set(t),n}function N_(e,t){let n=Bo(),r=Vo(),i=Ho();return Og(r,n,n[11],i,e,t),N_}var P_={};function F_(e){rd(`NgLet`);let t=Vo(),n=Bo(),r=e+27;return Wo(Xf(t,r,128,null,null),!1),yo(t,n,r,P_),F_}function I_(e){return yo(Vo(),Bo(),ls(),e),e}function L_(e){let t=vo(qo(),27+e);if(t===P_)throw new pi(314,!1);return t}function R_(e,t,n=``){return w_(Bo(),e,t,n)}function z_(e,t,n){let r=Vo();r.firstCreatePass&&B_(t,r.data,r.blueprint,co(e),n)}function B_(e,t,n,r,i){if(e=yi(e),Array.isArray(e))for(let a=0;a>20;if(Fa(e)||!e.multi){let r=new Uc(l,i,Up,null),p=U_(c,t,i?u:u+f,d);p===-1?(fl(cl(s,o),a,c),V_(a,e,t.length),t.push(c),s.directiveStart++,s.directiveEnd++,i&&(s.providerIndexes+=1048576),n.push(r),o.push(r)):(n[p]=r,o[p]=r)}else{let p=U_(c,t,u+f,d),m=U_(c,t,u,u+f),ee=p>=0&&n[p],h=m>=0&&n[m];if(i&&!h||!i&&!ee){fl(cl(s,o),a,c);let u=tne(i?ene:$te,n.length,i,r,l,e);!i&&h&&(n[m].providerFactory=u),V_(a,e,t.length,0),t.push(c),s.directiveStart++,s.directiveEnd++,i&&(s.providerIndexes+=1048576),n.push(u),o.push(u)}else{let t=H_(n[i?m:p],l,!i&&r);V_(a,e,p>-1?p:m,t)}!i&&r&&h&&n[m].componentProviders++}}}function V_(e,t,n,r){let i=Fa(t),a=Ia(t);if(i||a){let o=(a?yi(t.useClass):t).prototype.ngOnDestroy;if(o){let a=e.destroyHooks||=[];if(!i&&t.multi){let e=a.indexOf(n);e===-1?a.push(n,[r,o]):a[e+1].push(r,o)}else a.push(n,o)}}}function H_(e,t,n){return n&&e.componentProviders++,e.multi.push(t)-1}function U_(e,t,n,r){for(let i=n;i{n.providersResolver=(t,n)=>z_(t,n?n(e):e,!1),t&&(n.viewProvidersResolver=(e,n)=>z_(e,n?n(t):t,!0))}}function K_(e,t){let n=Xo()+e,r=Bo();return r[n]===Fu?mp(r,n,t()):hp(r,n)}function q_(e,t,n){return X_(Bo(),Xo(),e,t,n)}function J_(e,t,n,r){return Z_(Bo(),Xo(),e,t,n,r)}function nne(e,t,n,r,i,a,o){return rne(Bo(),Xo(),e,t,n,r,i,a)}function Y_(e,t){let n=e[t];return n===Fu?void 0:n}function X_(e,t,n,r,i,a){let o=t+n;return gp(e,o,i)?mp(e,o+1,a?r.call(a,i):r(i)):Y_(e,o+1)}function Z_(e,t,n,r,i,a,o){let s=t+n;return _p(e,s,i,a)?mp(e,s+2,o?r.call(o,i,a):r(i,a)):Y_(e,s+2)}function rne(e,t,n,r,i,a,o,s,c){let l=t+n;return yp(e,l,i,a,o,s)?mp(e,l+4,c?r.call(c,i,a,o,s):r(i,a,o,s)):Y_(e,l+4)}function Q_(e,t){let n=Vo(),r,i=e+27;n.firstCreatePass?(r=ine(t,n.pipeRegistry),n.data[i]=r,r.onDestroy&&(n.destroyHooks??=[]).push(i,r.onDestroy)):r=n.data[i];let a=r.factory||(r.factory=ca(r.type,!0)),o=Xi(Up);try{let e=nl(!1),t=a();return nl(e),yo(n,Bo(),i,t),t}finally{Xi(o)}}function ine(e,t){if(t)for(let n=t.length-1;n>=0;n--){let r=t[n];if(e===r.name)return r}}function $_(e,t,n){let r=e+27,i=Bo(),a=vo(i,r);return tv(i,r)?X_(i,Xo(),t,a.transform,n,a):a.transform(n)}function ev(e,t,n,r){let i=e+27,a=Bo(),o=vo(a,i);return tv(a,i)?Z_(a,Xo(),t,o.transform,n,r,o):o.transform(n,r)}function tv(e,t){return e[1].data[t].pure}function nv(e,t){return Yf(e,t)}var ane=(()=>{class e{applicationErrorHandler=x(qs);appRef=x(zh);taskService=x(Es);ngZone=x(Ps);zonelessEnabled=x(ic);tracing=x(td,{optional:!0});zoneIsDefined=typeof Zone<`u`&&!!Zone.root.run;schedulerTickApplyArgs=[{data:{__scheduler_tick__:!0}}];subscriptions=new cr;angularZoneId=this.zoneIsDefined?this.ngZone._inner?.get(Ms):null;scheduleInRootZone=!this.zonelessEnabled&&this.zoneIsDefined&&(x(ac,{optional:!0})??!1);cancelScheduledCallback=null;useMicrotaskScheduler=!1;runningTick=!1;pendingRenderTaskId=null;constructor(){this.subscriptions.add(this.appRef.afterTick.subscribe(()=>{let e=this.taskService.add();if(!this.runningTick&&(this.cleanup(),!this.zonelessEnabled||this.appRef.includeAllTestViews)){this.taskService.remove(e);return}this.switchToMicrotaskScheduler(),this.taskService.remove(e)})),this.subscriptions.add(this.ngZone.onUnstable.subscribe(()=>{this.runningTick||this.cleanup()}))}switchToMicrotaskScheduler(){this.ngZone.runOutsideAngular(()=>{let e=this.taskService.add();this.useMicrotaskScheduler=!0,queueMicrotask(()=>{this.useMicrotaskScheduler=!1,this.taskService.remove(e)})})}notify(e){if(!this.zonelessEnabled&&e===5)return;switch(e){case 0:case 2:this.appRef.dirtyFlags|=2;break;case 3:case 4:case 5:case 1:this.appRef.dirtyFlags|=4;break;case 6:this.appRef.dirtyFlags|=2;break;case 12:this.appRef.dirtyFlags|=16;break;case 13:this.appRef.dirtyFlags|=2;break;case 11:break;default:this.appRef.dirtyFlags|=8}if(this.appRef.tracingSnapshot=this.tracing?.snapshot(this.appRef.tracingSnapshot)??null,!this.shouldScheduleTick())return;let t=this.useMicrotaskScheduler?As:ks;this.pendingRenderTaskId=this.taskService.add(),this.cancelScheduledCallback=this.scheduleInRootZone?Zone.root.run(()=>t(()=>this.tick())):this.ngZone.runOutsideAngular(()=>t(()=>this.tick()))}shouldScheduleTick(){return!(this.appRef.destroyed||this.pendingRenderTaskId!==null||this.runningTick||this.appRef._runningTick||!this.zonelessEnabled&&this.zoneIsDefined&&Zone.current.get(`isAngularZone_ID`+this.angularZoneId))}tick(){if(this.runningTick||this.appRef.destroyed)return;if(this.appRef.dirtyFlags===0){this.cleanup();return}!this.zonelessEnabled&&this.appRef.dirtyFlags&7&&(this.appRef.dirtyFlags|=1);let e=this.taskService.add();try{this.ngZone.run(()=>{this.runningTick=!0,this.appRef._tick()},void 0,this.schedulerTickApplyArgs)}catch(e){this.applicationErrorHandler(e)}finally{this.taskService.remove(e),this.cleanup()}}ngOnDestroy(){this.subscriptions.unsubscribe(),this.cleanup()}cleanup(){if(this.runningTick=!1,this.cancelScheduledCallback?.(),this.cancelScheduledCallback=null,this.pendingRenderTaskId!==null){let e=this.pendingRenderTaskId;this.pendingRenderTaskId=null,this.taskService.remove(e)}}static ɵfac=function(t){return new(t||e)};static ɵprov=kl({token:e,factory:e.ɵfac})}return e})();function one(){return[{provide:rc,useExisting:ane},{provide:Ps,useClass:Hs},{provide:ic,useValue:!0}]}function sne(){return typeof $localize<`u`&&$localize.locale||`en-US`}var rv=new Oi(``,{factory:()=>x(rv,{optional:!0,skipSelf:!0})||sne()}),iv=class{destroyed=!1;listeners=null;errorHandler=x(Ks,{optional:!0});isEmitting=!1;hasNullListeners=!1;destroyRef=x(Ss);constructor(){this.destroyRef.onDestroy(()=>{this.destroyed=!0,this.listeners=null})}subscribe(e){if(this.destroyed)throw new pi(953,!1);return(this.listeners??=[]).push(e),{unsubscribe:()=>{let t=this.listeners?this.listeners.indexOf(e):-1;t>-1&&(this.isEmitting?(this.hasNullListeners=!0,this.listeners[t]=null):this.listeners.splice(t,1))}}}emit(e){if(this.destroyed){console.warn(mi(953,!1));return}if(this.listeners===null)return;this.isEmitting=!0;let t=an(null);try{for(let t of this.listeners)try{t!==null&&t(e)}catch(e){this.errorHandler?.handleError(e)}}finally{this.hasNullListeners&&(this.hasNullListeners=!1,this.listeners&&cne(this.listeners)),an(t),this.isEmitting=!1}}};function cne(e){let t=e.length-1;for(;t>-1;)e[t]===null&&e.splice(t,1),t--}function X(e,t){return En(e,t?.equal)}function Z(e){return qn(e)}(class e extends Error{_brand;constructor(e){super(e)}static IDLE=new e(`IDLE`);static LOADING=new e(`LOADING`)});var lne=e=>e;function Q(e,t){return typeof e==`function`?av(Un(e,lne,t?.equal),t?.debugName,t?.set):av(Un(e.source,e.computation,e.equal),e.debugName,e.set)}function av(e,t,n){let r=e[rn],i=e;if(n!==void 0){let t=e=>Wn(r,e);i.set=e=>n(e,t),i.update=r=>n(r(Z(e)),t)}else i.set=e=>Wn(r,e),i.update=e=>Gn(r,e);return i.asReadonly=Ys.bind(e),i}var ov=!1;function une(){return ov}function sv(e){ov=e}function cv(e,t){let n=Object.create(vc);n.value=e,n.transformFn=t?.transform;function r(){if(cn(n),n.value===_c)throw new pi(-950,null);return n.value}return r[rn]=n,r}function lv(e){return new iv}function uv(e,t){return cv(e,t)}function dne(e){return cv(_c,e)}var $=(uv.required=dne,uv);function dv(e,t){let n=Object.create(vc),r=new iv;n.value=e;function i(){return cn(n),fv(n.value),n.value}return i[rn]=n,i.asReadonly=Ys.bind(i),i.set=e=>{n.equal(n.value,e)||(Mn(n,e),r.emit(e))},i.update=e=>{fv(n.value),i.set(e(n.value))},i.subscribe=r.subscribe.bind(r),i.destroyRef=r.destroyRef,i}function fv(e){if(e===_c)throw new pi(952,!1)}function pv(e,t){return dv(e,t)}function fne(e){return dv(_c,e)}var mv=(pv.required=fne,pv);function hv(e,t){return $m(t)}function pne(e,t){return eh(t)}var gv=(hv.required=pne,hv),mne=(()=>{class e{static __NG_ELEMENT_ID__=hne}return e})();function hne(e){return gne(Ho(),Bo(),(e&16)==16)}function gne(e,t,n){if(oo(e)&&!n){let n=bo(e.index,t);return new Kf(n,n)}if(e.type&175){let e=t[15];return new Kf(e,t)}return null}var _v=new Oi(``),_ne=new Oi(``);function vv(e){return!e.moduleRef}function vne(e){let t=vv(e)?e.r3Injector:e.moduleRef.injector,n=t.get(Ps);return n.run(()=>{vv(e)?e.r3Injector.resolveInjectorInitializers():e.moduleRef.resolveInjectorInitializers();let r=t.get(qs),i;if(n.runOutsideAngular(()=>{i=n.onError.subscribe({next:r})}),vv(e)){let n=()=>t.destroy(),r=e.platformInjector.get(_v);r.add(n),t.onDestroy(()=>{i.unsubscribe(),r.delete(n)})}else{let t=()=>e.moduleRef.destroy(),n=e.platformInjector.get(_v);n.add(t),e.moduleRef.onDestroy(()=>{Vh(e.allPlatformModules,e.moduleRef),i.unsubscribe(),n.delete(t)})}return bne(r,n,()=>{let n=t.get(Es),r=n.add(),i=t.get(yh);return i.runInitializers(),i.donePromise.then(()=>{if(Dg(t.get(rv,Eg)||`en-US`),!t.get(_ne,!0))return vv(e)?t.get(zh):(e.allPlatformModules.push(e.moduleRef),e.moduleRef);if(vv(e)){let n=t.get(zh);return e.rootComponent!==void 0&&n.bootstrap(e.rootComponent),n}return yne?.(e.moduleRef,e.allPlatformModules),e.moduleRef}).finally(()=>void n.remove(r))})})}var yne;function bne(e,t,n){try{let r=n();return rh(r)?r.catch(n=>{throw t.runOutsideAngular(()=>e(n)),n}):r}catch(n){throw t.runOutsideAngular(()=>e(n)),n}}var yv=null;function xne(e=[],t){return bs.create({name:t,providers:[{provide:La,useValue:`platform`},{provide:_v,useValue:new Set([()=>yv=null])},...e]})}function Sne(e=[]){if(yv)return yv;let t=xne(e);return yv=t,Lh(),Cne(t),t}function Cne(e){let t=e.get(Qs,null);eo(e,()=>{t?.forEach(e=>e())})}function wne(e){let{rootComponent:t,appProviders:n,platformProviders:r,platformRef:i}=e;Nc(bc.BootstrapApplicationStart);try{let e=i?.injector??Sne(r);return vne({r3Injector:new oh({providers:[one(),Js,...n||[]],parent:e,debugName:``,runEnvironmentInitializers:!1}).injector,platformInjector:e,rootComponent:t})}catch(e){return Promise.reject(e)}finally{Nc(bc.BootstrapApplicationEnd)}}function bv(e){return typeof e==`boolean`?e:e!=null&&e!==`false`}var xv=Symbol(`NOT_SET`),Sv=new Set,Tne={...Pn,kind:`afterRenderEffectPhase`,consumerIsAlwaysLive:!0,consumerAllowSignalWrites:!0,value:xv,cleanup:null,consumerMarkedDirty(){if(this.sequence.impl.executing){if(this.sequence.lastPhase===null||this.sequence.lastPhase(cn(r),r.value),r.signal[rn]=r,r.registerCleanupFn=e=>(r.cleanup??=new Set).add(e),this.nodes[e]=r,this.hooks[e]=e=>r.phaseFn(e)}}afterRun(){super.afterRun(),this.lastPhase=null}destroy(){if(this.onDestroyFns!==null)for(let e of this.onDestroyFns)e();super.destroy();for(let e of this.nodes)if(e)try{for(let t of e.cleanup??Sv)t()}finally{bn(e)}}};function Cv(e,t){let n=t?.injector??x(bs),r=n.get(rc),i=n.get(id),a=n.get(td,null,{optional:!0});i.impl??=n.get(od);let o=e;typeof o==`function`&&(o={mixedReadWrite:e});let s=n.get(tc,null,{optional:!0}),c=new Ene(i.impl,[o.earlyRead,o.write,o.mixedReadWrite,o.read],s?.view,r,n,a?.snapshot(null));return i.impl.register(c),c}var wv=null;function Tv(){return wv}function Dne(e){wv??=e}var One=class{},Ev=(function(e){return e[e.Format=0]=`Format`,e[e.Standalone=1]=`Standalone`,e})(Ev||{}),Dv=(function(e){return e[e.Narrow=0]=`Narrow`,e[e.Abbreviated=1]=`Abbreviated`,e[e.Wide=2]=`Wide`,e[e.Short=3]=`Short`,e})(Dv||{}),Ov=(function(e){return e[e.Short=0]=`Short`,e[e.Medium=1]=`Medium`,e[e.Long=2]=`Long`,e[e.Full=3]=`Full`,e})(Ov||{}),kv={Decimal:0,Group:1,List:2,PercentSign:3,PlusSign:4,MinusSign:5,Exponential:6,SuperscriptingExponent:7,PerMille:8,Infinity:9,NaN:10,TimeSeparator:11,CurrencyDecimal:12,CurrencyGroup:13};function kne(e){return Sg(e)[wg.LocaleId]}function Ane(e,t,n){let r=Sg(e);return Fv(Fv([r[wg.DayPeriodsFormat],r[wg.DayPeriodsStandalone]],t),n)}function jne(e,t,n){let r=Sg(e);return Fv(Fv([r[wg.DaysFormat],r[wg.DaysStandalone]],t),n)}function Mne(e,t,n){let r=Sg(e);return Fv(Fv([r[wg.MonthsFormat],r[wg.MonthsStandalone]],t),n)}function Nne(e,t){let n=Sg(e)[wg.Eras];return Fv(n,t)}function Av(e,t){return Fv(Sg(e)[wg.DateFormat],t)}function jv(e,t){return Fv(Sg(e)[wg.TimeFormat],t)}function Mv(e,t){let n=Sg(e)[wg.DateTimeFormat];return Fv(n,t)}function Nv(e,t){let n=Sg(e),r=n[wg.NumberSymbols][t];if(r===void 0){if(t===kv.CurrencyDecimal)return n[wg.NumberSymbols][kv.Decimal];if(t===kv.CurrencyGroup)return n[wg.NumberSymbols][kv.Group]}return r}function Pv(e){if(!e[wg.ExtraData])throw new pi(2303,!1)}function Pne(e){let t=Sg(e);return Pv(t),(t[wg.ExtraData][2]||[]).map(e=>typeof e==`string`?Iv(e):[Iv(e[0]),Iv(e[1])])}function Fne(e,t,n){let r=Sg(e);return Pv(r),Fv(Fv([r[wg.ExtraData][0],r[wg.ExtraData][1]],t)||[],n)||[]}function Fv(e,t){for(let n=t;n>-1;n--)if(e[n]!==void 0)return e[n];throw new pi(2304,!1)}function Iv(e){let[t,n]=e.split(`:`);return{hours:+t,minutes:+n}}var Ine=/^(\d{4,})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/,Lv=Object.create(null),Lne=/((?:[^BEGHLMOSWYZabcdhmswyz']+)|(?:'(?:[^']|'')*')|(?:G{1,5}|y{1,4}|Y{1,4}|M{1,5}|L{1,5}|w{1,2}|W{1}|d{1,2}|E{1,6}|c{1,6}|a{1,5}|b{1,5}|B{1,5}|h{1,2}|H{1,2}|m{1,2}|s{1,2}|S{1,3}|z{1,4}|Z{1,5}|O{1,4}))([\s\S]*)/,Rne=256;function zne(e,t,n,r){let i=Yne(e);Bne(t),t=zv(n,t)||t;let a=[],o;for(;t;)if(o=Lne.exec(t),o){a=a.concat(o.slice(1));let e=a.pop();if(!e)break;t=e}else{a.push(t);break}let s=i.getTimezoneOffset();r&&(s=Xv(r,s),i=Jne(i,r));let c=``;return a.forEach(e=>{let t=Kne(e);c+=t?t(i,n,s):e===`''`?`'`:e.replace(/(^'|'$)/g,``).replace(/''/g,`'`)}),c}function Bne(e){if(e.length>Rne)throw new pi(2300,!1)}function Rv(e,t,n){let r=new Date(0);return r.setFullYear(e,t,n),r.setHours(0,0,0),r}function zv(e,t){let n=kne(e);if(Lv[n]??=Object.create(null),Lv[n][t])return Lv[n][t];let r=``;switch(t){case`shortDate`:r=Av(e,Ov.Short);break;case`mediumDate`:r=Av(e,Ov.Medium);break;case`longDate`:r=Av(e,Ov.Long);break;case`fullDate`:r=Av(e,Ov.Full);break;case`shortTime`:r=jv(e,Ov.Short);break;case`mediumTime`:r=jv(e,Ov.Medium);break;case`longTime`:r=jv(e,Ov.Long);break;case`fullTime`:r=jv(e,Ov.Full);break;case`short`:let t=zv(e,`shortTime`),n=zv(e,`shortDate`);r=Bv(Mv(e,Ov.Short),[t,n]);break;case`medium`:let i=zv(e,`mediumTime`),a=zv(e,`mediumDate`);r=Bv(Mv(e,Ov.Medium),[i,a]);break;case`long`:let o=zv(e,`longTime`),s=zv(e,`longDate`);r=Bv(Mv(e,Ov.Long),[o,s]);break;case`full`:let c=zv(e,`fullTime`),l=zv(e,`fullDate`);r=Bv(Mv(e,Ov.Full),[c,l])}return r&&(Lv[n][t]=r),r}function Bv(e,t){return t&&(e=e.replace(/\{([^}]+)}/g,function(e,n){return Object.hasOwn(t,n)?t[n]:e})),e}function Vv(e,t,n=`-`,r,i){let a=``;(e<0||i&&e<=0)&&(i?e=-e+1:(e=-e,a=n));let o=String(e);for(;o.length0||s>-n)&&(s+=n),e===3)s===0&&n===-12&&(s=12);else if(e===6)return Vne(s,t);let c=Nv(o,kv.MinusSign);return Vv(s,t,c,r,i)}}function Hne(e,t){switch(e){case 0:return t.getFullYear();case 1:return t.getMonth();case 2:return t.getDate();case 3:return t.getHours();case 4:return t.getMinutes();case 5:return t.getSeconds();case 6:return t.getMilliseconds();case 7:return t.getDay();default:throw new pi(2301,!1)}}function Uv(e,t,n=Ev.Format,r=!1){return function(i,a){return Une(i,a,e,t,n,r)}}function Une(e,t,n,r,i,a){switch(n){case 2:return Mne(t,i,r)[e.getMonth()];case 1:return jne(t,i,r)[e.getDay()];case 0:let n=e.getHours(),o=e.getMinutes();if(a){let e=Pne(t),a=Fne(t,i,r),s=e.findIndex(e=>{if(Array.isArray(e)){let[t,r]=e,i=n>=t.hours&&o>=t.minutes,a=n0?Math.floor(i/60):Math.ceil(i/60);switch(e){case 0:return(i>=0?`+`:``)+Vv(o,2,a)+Vv(Math.abs(i%60),2,a);case 1:return`GMT`+(i>=0?`+`:``)+Vv(o,1,a);case 2:return`GMT`+(i>=0?`+`:``)+Vv(o,2,a)+`:`+Vv(Math.abs(i%60),2,a);case 3:return r===0?`Z`:(i>=0?`+`:``)+Vv(o,2,a)+`:`+Vv(Math.abs(i%60),2,a);default:throw new pi(2310,!1)}}}var Wne=0,Gv=4;function Gne(e){let t=Rv(e,Wne,1).getDay();return Rv(e,0,1+(t<=Gv?Gv:11)-t)}function Kv(e){let t=e.getDay(),n=t===0?-3:Gv-t;return Rv(e.getFullYear(),e.getMonth(),e.getDate()+n)}function qv(e,t=!1){return function(n,r){let i;if(t){let e=new Date(n.getFullYear(),n.getMonth(),1).getDay()-1,t=n.getDate();i=1+Math.floor((t+e)/7)}else{let e=Kv(n),t=Gne(e.getFullYear()),r=e.getTime()-t.getTime();i=1+Math.round(r/6048e5)}return Vv(i,e,Nv(r,kv.MinusSign))}}function Jv(e,t=!1){return function(n,r){return Vv(Kv(n).getFullYear(),e,Nv(r,kv.MinusSign),t)}}var Yv=Object.create(null);function Kne(e){if(Yv[e])return Yv[e];let t;switch(e){case`G`:case`GG`:case`GGG`:t=Uv(3,Dv.Abbreviated);break;case`GGGG`:t=Uv(3,Dv.Wide);break;case`GGGGG`:t=Uv(3,Dv.Narrow);break;case`y`:t=Hv(0,1,0,!1,!0);break;case`yy`:t=Hv(0,2,0,!0,!0);break;case`yyy`:t=Hv(0,3,0,!1,!0);break;case`yyyy`:t=Hv(0,4,0,!1,!0);break;case`Y`:t=Jv(1);break;case`YY`:t=Jv(2,!0);break;case`YYY`:t=Jv(3);break;case`YYYY`:t=Jv(4);break;case`M`:case`L`:t=Hv(1,1,1);break;case`MM`:case`LL`:t=Hv(1,2,1);break;case`MMM`:t=Uv(2,Dv.Abbreviated);break;case`MMMM`:t=Uv(2,Dv.Wide);break;case`MMMMM`:t=Uv(2,Dv.Narrow);break;case`LLL`:t=Uv(2,Dv.Abbreviated,Ev.Standalone);break;case`LLLL`:t=Uv(2,Dv.Wide,Ev.Standalone);break;case`LLLLL`:t=Uv(2,Dv.Narrow,Ev.Standalone);break;case`w`:t=qv(1);break;case`ww`:t=qv(2);break;case`W`:t=qv(1,!0);break;case`d`:t=Hv(2,1);break;case`dd`:t=Hv(2,2);break;case`c`:case`cc`:t=Hv(7,1);break;case`ccc`:t=Uv(1,Dv.Abbreviated,Ev.Standalone);break;case`cccc`:t=Uv(1,Dv.Wide,Ev.Standalone);break;case`ccccc`:t=Uv(1,Dv.Narrow,Ev.Standalone);break;case`cccccc`:t=Uv(1,Dv.Short,Ev.Standalone);break;case`E`:case`EE`:case`EEE`:t=Uv(1,Dv.Abbreviated);break;case`EEEE`:t=Uv(1,Dv.Wide);break;case`EEEEE`:t=Uv(1,Dv.Narrow);break;case`EEEEEE`:t=Uv(1,Dv.Short);break;case`a`:case`aa`:case`aaa`:t=Uv(0,Dv.Abbreviated);break;case`aaaa`:t=Uv(0,Dv.Wide);break;case`aaaaa`:t=Uv(0,Dv.Narrow);break;case`b`:case`bb`:case`bbb`:t=Uv(0,Dv.Abbreviated,Ev.Standalone,!0);break;case`bbbb`:t=Uv(0,Dv.Wide,Ev.Standalone,!0);break;case`bbbbb`:t=Uv(0,Dv.Narrow,Ev.Standalone,!0);break;case`B`:case`BB`:case`BBB`:t=Uv(0,Dv.Abbreviated,Ev.Format,!0);break;case`BBBB`:t=Uv(0,Dv.Wide,Ev.Format,!0);break;case`BBBBB`:t=Uv(0,Dv.Narrow,Ev.Format,!0);break;case`h`:t=Hv(3,1,-12);break;case`hh`:t=Hv(3,2,-12);break;case`H`:t=Hv(3,1);break;case`HH`:t=Hv(3,2);break;case`m`:t=Hv(4,1);break;case`mm`:t=Hv(4,2);break;case`s`:t=Hv(5,1);break;case`ss`:t=Hv(5,2);break;case`S`:t=Hv(6,1);break;case`SS`:t=Hv(6,2);break;case`SSS`:t=Hv(6,3);break;case`Z`:case`ZZ`:case`ZZZ`:t=Wv(0);break;case`ZZZZZ`:t=Wv(3);break;case`O`:case`OO`:case`OOO`:case`z`:case`zz`:case`zzz`:t=Wv(1);break;case`OOOO`:case`ZZZZ`:case`zzzz`:t=Wv(2);break;default:return null}return Yv[e]=t,t}function Xv(e,t){e=e.replace(/:/g,``);let n=Date.parse(`Jan 01, 1970 00:00:00 `+e)/6e4;return isNaN(n)?t:n}function qne(e,t){return e=new Date(e.getTime()),e.setMinutes(e.getMinutes()+t),e}function Jne(e,t,n){let r=e.getTimezoneOffset();return qne(e,-1*(Xv(t,r)-r))}function Yne(e){if(Zv(e))return e;if(typeof e==`number`&&!isNaN(e))return new Date(e);if(typeof e==`string`){if(e=e.trim(),/^(\d{4}(-\d{1,2}(-\d{1,2})?)?)$/.test(e)){let[t,n=1,r=1]=e.split(`-`).map(e=>+e);return Rv(t,n-1,r)}let t=parseFloat(e);if(!isNaN(e-t))return new Date(t);let n;if(n=e.match(Ine))return Xne(n)}let t=new Date(e);if(!Zv(t))throw new pi(2311,!1);return t}function Xne(e){let t=new Date(0),n=0,r=0,i=e[8]?t.setUTCFullYear:t.setFullYear,a=e[8]?t.setUTCHours:t.setHours;e[9]&&(n=Number(e[9]+e[10]),r=Number(e[9]+e[11])),i.call(t,Number(e[1]),Number(e[2])-1,Number(e[3]));let o=Number(e[4]||0)-n,s=Number(e[5]||0)-r,c=Number(e[6]||0),l=Math.floor(parseFloat(`0.`+(e[7]||0))*1e3);return a.call(t,o,s,c,l),t}function Zv(e){return e instanceof Date&&!isNaN(e.valueOf())}var Qv=(()=>{class e{_viewContainerRef;_viewRef=null;ngTemplateOutletContext=null;ngTemplateOutlet=null;ngTemplateOutletInjector=null;injector=x(bs);constructor(e){this._viewContainerRef=e}ngOnChanges(e){if(this._shouldRecreateView(e)){let e=this._viewContainerRef;if(this._viewRef&&e.remove(e.indexOf(this._viewRef)),!this.ngTemplateOutlet){this._viewRef=null;return}let t=this._createContextForwardProxy();this._viewRef=e.createEmbeddedView(this.ngTemplateOutlet,t,{injector:this._getInjector()})}}_getInjector(){return this.ngTemplateOutletInjector===`outlet`?this.injector:this.ngTemplateOutletInjector??void 0}_shouldRecreateView(e){return!!e.ngTemplateOutlet||!!e.ngTemplateOutletInjector}_createContextForwardProxy(){return new Proxy({},{set:(e,t,n)=>this.ngTemplateOutletContext?Reflect.set(this.ngTemplateOutletContext,t,n):!1,get:(e,t,n)=>{if(this.ngTemplateOutletContext)return Reflect.get(this.ngTemplateOutletContext,t,n)}})}static ɵfac=function(t){return new(t||e)(Up(Cm))};static ɵdir=fh({type:e,selectors:[[``,`ngTemplateOutlet`,``]],inputs:{ngTemplateOutletContext:`ngTemplateOutletContext`,ngTemplateOutlet:`ngTemplateOutlet`,ngTemplateOutletInjector:`ngTemplateOutletInjector`},features:[wc]})}return e})();function Zne(e,t){return new pi(2100,!1)}var Qne=`mediumDate`,$ne=new Oi(``),ere=new Oi(``),$v=(()=>{class e{locale;defaultTimezone;defaultOptions;constructor(e,t,n){this.locale=e,this.defaultTimezone=t,this.defaultOptions=n}transform(t,n,r,i){if(t==null||t===``||t!==t)return null;try{let e=n??this.defaultOptions?.dateFormat??Qne,a=r??this.defaultOptions?.timezone??this.defaultTimezone??void 0;return zne(t,e,i||this.locale,a)}catch(t){throw Zne(e,t.message)}}static ɵfac=function(t){return new(t||e)(Up(rv,16),Up($ne,24),Up(ere,24))};static ɵpipe=ph({name:`date`,type:e,pure:!0})}return e})(),ey=(()=>{class e{transform(e){return JSON.stringify(e,null,2)}static ɵfac=function(t){return new(t||e)};static ɵpipe=ph({name:`json`,type:e,pure:!1})}return e})();function tre(e,t){t=encodeURIComponent(t);for(let n of e.split(`;`)){let e=n.indexOf(`=`),[r,i]=e==-1?[n,``]:[n.slice(0,e),n.slice(e+1)];if(r.trim()!==t)continue;let a=i;try{a=decodeURIComponent(i)}catch{}return a.length>1&&a[0]===`"`&&a[a.length-1]===`"`&&(a=a.slice(1,-1)),a}return null}var nre=`browser`,ty=class{_doc;constructor(e){this._doc=e}manager},ny=(()=>{class e extends ty{constructor(e){super(e)}supports(e){return!0}addEventListener(e,t,n,r){return e.addEventListener(t,n,r),()=>this.removeEventListener(e,t,n,r)}removeEventListener(e,t,n,r){return e.removeEventListener(t,n,r)}static ɵfac=function(t){return new(t||e)(ra(xs))};static ɵprov=xi({token:e,factory:e.ɵfac})}return e})(),ry=new Oi(``),iy=(()=>{class e{_zone;_plugins;_eventNameToPlugin=new Map;constructor(e,t){this._zone=t,e.forEach(e=>{e.manager=this});let n=e.filter(e=>!(e instanceof ny));this._plugins=n.slice().reverse();let r=e.find(e=>e instanceof ny);r&&this._plugins.push(r)}addEventListener(e,t,n,r){return this._findPluginFor(t).addEventListener(e,t,n,r)}getZone(){return this._zone}_findPluginFor(e){let t=this._eventNameToPlugin.get(e);if(t)return t;if(t=this._plugins.find(t=>t.supports(e)),!t)throw new pi(-5101,!1);return this._eventNameToPlugin.set(e,t),t}static ɵfac=function(t){return new(t||e)(ra(ry),ra(Ps))};static ɵprov=xi({token:e,factory:e.ɵfac})}return e})(),ay=`ng-app-id`;function oy(e){for(let t of e)t.remove()}function sy(e,t){let n=t.createElement(`style`);return n.textContent=e,n}function rre(e,t,n,r){let i=e.head?.querySelectorAll(`style[${ay}="${t}"],link[${ay}="${t}"]`);if(!i||i.length===0)return!1;for(let e of i)e.removeAttribute(ay),e instanceof HTMLLinkElement?r.set(e.href.slice(e.href.lastIndexOf(`/`)+1),{usage:0,elements:[e]}):e.textContent&&n.set(e.textContent,{usage:0,elements:[e]});return!0}function cy(e,t){let n=t.createElement(`link`);return n.setAttribute(`rel`,`stylesheet`),n.setAttribute(`href`,e),n}var ly=(()=>{class e{doc;appId;nonce;inline=new Map;external=new Map;hosts=new Set;constructor(e,t,n,r={}){this.doc=e,this.appId=t,this.nonce=n,rre(e,t,this.inline,this.external)&&this.hosts.add(e.head)}addStyles(e,t){for(let t of e)this.addUsage(t,this.inline,sy);t?.forEach(e=>this.addUsage(e,this.external,cy))}removeStyles(e,t){for(let t of e)this.removeUsage(t,this.inline);t?.forEach(e=>this.removeUsage(e,this.external))}addUsage(e,t,n){let r=t.get(e);r?r.usage++:t.set(e,{usage:1,elements:[...this.hosts].map(t=>this.addElement(t,n(e,this.doc)))})}removeUsage(e,t){let n=t.get(e);n&&(n.usage--,n.usage<=0&&(oy(n.elements),t.delete(e)))}ngOnDestroy(){for(let[,{elements:e}]of[...this.inline,...this.external])oy(e);this.hosts.clear()}addHost(e){if(!this.hosts.has(e)){this.hosts.add(e);for(let[t,{elements:n}]of this.inline)n.push(this.addElement(e,sy(t,this.doc)));for(let[t,{elements:n}]of this.external)n.push(this.addElement(e,cy(t,this.doc)))}}removeHost(e){this.hosts.delete(e);for(let t of[...this.inline.values(),...this.external.values()]){let n=[];for(let r of t.elements)r.parentNode===e?r.remove():n.push(r);t.elements=n}}addElement(e,t){return this.nonce&&t.setAttribute(`nonce`,this.nonce),e.appendChild(t)}static ɵfac=function(t){return new(t||e)(ra(xs),ra(Xs),ra(ec,8),ra($s))};static ɵprov=xi({token:e,factory:e.ɵfac})}return e})(),uy={svg:`http://www.w3.org/2000/svg`,xhtml:`http://www.w3.org/1999/xhtml`,xlink:`http://www.w3.org/1999/xlink`,xml:`http://www.w3.org/XML/1998/namespace`,xmlns:`http://www.w3.org/2000/xmlns/`,math:`http://www.w3.org/1998/Math/MathML`},dy=/%COMP%/g,fy=`%COMP%`,ire=`_nghost-${fy}`,are=`_ngcontent-${fy}`,ore=!0,sre=new Oi(``,{factory:()=>ore}),cre=new Oi(``);function lre(e){return are.replace(dy,e)}function ure(e){return ire.replace(dy,e)}function py(e,t){return t.map(t=>t.replace(dy,e))}var my=(()=>{class e{eventManager;sharedStylesHost;appId;removeStylesOnCompDestroy;doc;ngZone;nonce;tracingService;rendererByCompId=new Map;defaultRenderer;cssVarNamespace;constructor(e,t,n,r,i,a,o=null,s=null,c=null){this.eventManager=e,this.sharedStylesHost=t,this.appId=n,this.removeStylesOnCompDestroy=r,this.doc=i,this.ngZone=a,this.nonce=o,this.tracingService=s,this.cssVarNamespace=c??``,this.defaultRenderer=new hy(e,i,a,this.tracingService,this.cssVarNamespace)}createRenderer(e,t){if(!e||!t)return this.defaultRenderer;let n=this.getOrCreateRenderer(e,t);return n instanceof yy?n.applyToHost(e):n instanceof vy&&n.applyStyles(),n}getOrCreateRenderer(e,t){let n=this.rendererByCompId,r=n.get(t.id);if(!r){let i=this.doc,a=this.ngZone,o=this.eventManager,s=this.sharedStylesHost,c=this.removeStylesOnCompDestroy,l=this.tracingService;switch(t.encapsulation){case su.Emulated:r=new yy(o,s,t,this.appId,c,i,a,l,this.cssVarNamespace);break;case su.ShadowDom:return new _y(o,e,t,i,a,this.nonce,l,this.cssVarNamespace,s);case su.ExperimentalIsolatedShadowDom:return new _y(o,e,t,i,a,this.nonce,l,this.cssVarNamespace);default:r=new vy(o,s,t,c,i,a,l,this.cssVarNamespace)}n.set(t.id,r)}return r}ngOnDestroy(){this.rendererByCompId.clear()}componentReplaced(e){this.rendererByCompId.delete(e)}static ɵfac=function(t){return new(t||e)(ra(iy),ra(Vp),ra(Xs),ra(sre),ra(xs),ra(Ps),ra(ec),ra(td,8),ra(cre,8))};static ɵprov=xi({token:e,factory:e.ɵfac})}return e})(),hy=class{eventManager;doc;ngZone;tracingService;cssVarNamespace;data=Object.create(null);throwOnSyntheticProps=!0;constructor(e,t,n,r,i=``){this.eventManager=e,this.doc=t,this.ngZone=n,this.tracingService=r,this.cssVarNamespace=i}destroy(){}destroyNode=null;createElement(e,t){return t?this.doc.createElementNS(uy[t]||t,e):this.doc.createElement(e)}createComment(e){return this.doc.createComment(e)}createText(e){return this.doc.createTextNode(e)}appendChild(e,t){(gy(e)?e.content:e).appendChild(t)}insertBefore(e,t,n){if(e){let r=gy(e)?e.content:e;if(n!=null&&n.parentNode!==r)throw new pi(-5106,!1);r.insertBefore(t,n)}}removeChild(e,t){t.remove()}selectRootElement(e,t){let n=typeof e==`string`?this.doc.querySelector(e):e;if(!n)throw new pi(-5104,!1);return t||(n.textContent=``),n}parentNode(e){return e.parentNode}nextSibling(e){return e.nextSibling}setAttribute(e,t,n,r){if(r){t=r+`:`+t;let i=uy[r];i?e.setAttributeNS(i,t,n):e.setAttribute(t,n)}else e.setAttribute(t,n)}removeAttribute(e,t,n){if(n){let r=uy[n];r?e.removeAttributeNS(r,t):e.removeAttribute(`${n}:${t}`)}else e.removeAttribute(t)}addClass(e,t){e.classList.add(t)}removeClass(e,t){e.classList.remove(t)}setStyle(e,t,n,r){let i=t.startsWith(`--`);i&&(t=t.replace(`%NS%`,this.cssVarNamespace)),i||r&(Iu.DashCase|Iu.Important)?e.style.setProperty(t,n,r&Iu.Important?`important`:``):e.style[t]=n}removeStyle(e,t,n){let r=t.startsWith(`--`);r&&(t=t.replace(`%NS%`,this.cssVarNamespace)),r||n&Iu.DashCase?e.style.removeProperty(t):e.style[t]=``}setProperty(e,t,n){e!=null&&(e[t]=n)}setValue(e,t){e.nodeValue=t}listen(e,t,n,r){if(typeof e==`string`&&(e=Tv().getGlobalEventTarget(this.doc,e),!e))throw new pi(-5102,!1);let i=this.decoratePreventDefault(n);return this.tracingService?.wrapEventListener&&(i=this.tracingService.wrapEventListener(e,t,i)),this.eventManager.addEventListener(e,t,i,r)}decoratePreventDefault(e){return t=>{if(t===`__ngUnwrap__`)return e;e(t)===!1&&t.preventDefault()}}};function gy(e){return e.tagName===`TEMPLATE`&&e.content!==void 0}var _y=class extends hy{hostEl;sharedStylesHost;shadowRoot;constructor(e,t,n,r,i,a,o,s,c){super(e,r,i,o,s),this.hostEl=t,this.sharedStylesHost=c,this.shadowRoot=t.attachShadow({mode:`open`}),this.sharedStylesHost&&this.sharedStylesHost.addHost(this.shadowRoot);let l=n.styles;l=py(n.id,l).map(e=>e.replace(/%NS%/g,s));for(let e of l){let t=document.createElement(`style`);a&&t.setAttribute(`nonce`,a),t.textContent=e,this.shadowRoot.appendChild(t)}let u=n.getExternalStyles?.();if(u)for(let e of u){let t=cy(e,r);a&&t.setAttribute(`nonce`,a),this.shadowRoot.appendChild(t)}}nodeOrShadowRoot(e){return e===this.hostEl?this.shadowRoot:e}appendChild(e,t){return super.appendChild(this.nodeOrShadowRoot(e),t)}insertBefore(e,t,n){return super.insertBefore(this.nodeOrShadowRoot(e),t,n)}removeChild(e,t){return super.removeChild(null,t)}parentNode(e){return this.nodeOrShadowRoot(super.parentNode(this.nodeOrShadowRoot(e)))}destroy(){this.sharedStylesHost&&this.sharedStylesHost.removeHost(this.shadowRoot)}},vy=class extends hy{sharedStylesHost;removeStylesOnCompDestroy;styles;styleUrls;constructor(e,t,n,r,i,a,o,s,c){super(e,i,a,o,s),this.sharedStylesHost=t,this.removeStylesOnCompDestroy=r;let l=n.styles,u=c?py(c,l):l;this.styles=u.map(e=>e.replace(/%NS%/g,s)),this.styleUrls=n.getExternalStyles?.(c)}applyStyles(){this.sharedStylesHost.addStyles(this.styles,this.styleUrls)}destroy(){this.removeStylesOnCompDestroy&&Uu.size===0&&this.sharedStylesHost.removeStyles(this.styles,this.styleUrls)}},yy=class extends vy{contentAttr;hostAttr;constructor(e,t,n,r,i,a,o,s,c){let l=r+`-`+n.id;super(e,t,n,i,a,o,s,c,l),this.contentAttr=lre(l),this.hostAttr=ure(l)}applyToHost(e){this.applyStyles(),this.setAttribute(e,this.hostAttr,``)}createElement(e,t){let n=super.createElement(e,t);return super.setAttribute(n,this.contentAttr,``),n}},dre=class e extends One{supportsDOMEvents=!0;static makeCurrent(){Dne(new e)}onAndCancel(e,t,n,r){return e.addEventListener(t,n,r),()=>{e.removeEventListener(t,n,r)}}dispatchEvent(e,t){e.dispatchEvent(t)}remove(e){e.remove()}createElement(e,t){return t||=this.getDefaultDocument(),t.createElement(e)}createHtmlDocument(){return document.implementation.createHTMLDocument(`fakeTitle`)}getDefaultDocument(){return document}isElementNode(e){return e.nodeType===Node.ELEMENT_NODE}isShadowRoot(e){return e instanceof DocumentFragment}getGlobalEventTarget(e,t){return t===`window`?window:t===`document`?e:t===`body`?e.body:null}getBaseHref(e){let t=fre();return t==null?null:pre(t)}resetBaseElement(){by=null}getUserAgent(){return window.navigator.userAgent}getCookie(e){return tre(document.cookie,e)}},by=null;function fre(){return by||=document.head.querySelector(`base`),by?by.getAttribute(`href`):null}function pre(e){return new URL(e,document.baseURI).pathname}var xy=[`alt`,`control`,`meta`,`shift`],mre={"\b":`Backspace`," ":`Tab`,"":`Delete`,"\x1B":`Escape`,Del:`Delete`,Esc:`Escape`,Left:`ArrowLeft`,Right:`ArrowRight`,Up:`ArrowUp`,Down:`ArrowDown`,Menu:`ContextMenu`,Scroll:`ScrollLock`,Win:`OS`},hre={alt:e=>e.altKey,control:e=>e.ctrlKey,meta:e=>e.metaKey,shift:e=>e.shiftKey},gre=(()=>{class e extends ty{constructor(e){super(e)}supports(t){return e.parseEventName(t)!=null}addEventListener(t,n,r,i){let a=e.parseEventName(n),o=e.eventCallback(a.fullKey,r,this.manager.getZone());return this.manager.getZone().runOutsideAngular(()=>Tv().onAndCancel(t,a.domEventName,o,i))}static parseEventName(t){let n=t.toLowerCase().split(`.`),r=n.shift();if(n.length===0||r!==`keydown`&&r!==`keyup`)return null;let i=e._normalizeKey(n.pop()),a=``,o=n.indexOf(`code`);if(o>-1&&(n.splice(o,1),a=`code.`),xy.forEach(e=>{let t=n.indexOf(e);t>-1&&(n.splice(t,1),a+=e+`.`)}),a+=i,n.length!=0||i.length===0)return null;let s={};return s.domEventName=r,s.fullKey=a,s}static matchEventFullKeyCode(e,t){let n=mre[e.key]||e.key,r=``;return t.indexOf(`code.`)>-1&&(n=e.code,r=`code.`),n==null||!n?!1:(n=n.toLowerCase(),n===` `?n=`space`:n===`.`&&(n=`dot`),xy.forEach(t=>{if(t!==n){let n=hre[t];n(e)&&(r+=t+`.`)}}),r+=n,r===t)}static eventCallback(t,n,r){return i=>{e.matchEventFullKeyCode(i,t)&&r.runGuarded(()=>n(i))}}static _normalizeKey(e){return e===`esc`?`escape`:e}static ɵfac=function(t){return new(t||e)(ra(xs))};static ɵprov=xi({token:e,factory:e.ɵfac})}return e})();async function _re(e,t,n){return wne({rootComponent:e,...vre(t,n)})}function vre(e,t){return{platformRef:t?.platformRef,appProviders:[...Cre,...e?.providers??[]],platformProviders:Sre}}function yre(){dre.makeCurrent()}function bre(){return new Ks}function xre(){return Yl(document),document}var Sre=[{provide:$s,useValue:nre},{provide:Qs,useValue:yre,multi:!0},{provide:xs,useFactory:xre}],Cre=[{provide:La,useValue:`root`},{provide:Ks,useFactory:bre},{provide:ry,useClass:ny,multi:!0},{provide:ry,useClass:gre,multi:!0},my,{provide:Vp,useClass:ly},{provide:ly,useExisting:Vp},iy,{provide:sp,useExisting:my},[]];function Sy(e,t){let n=`\x1B[${e}m`,r=`\x1B[${t}m`;return((e,...t)=>{if(Array.isArray(e)&&`raw`in e){let i=e,a=``;for(let e=0;e{let r=new Ere({code:i,why:Cy(a.why,e),fix:Cy(a.fix,e),docs:o,cause:e.cause,sources:e.sources,data:Cy(a.data,e)},s);for(let e of t)e(r,n);return r};n[i]=s}return n}function kre(e){return t=>{let n=`${e.bold(e.red(`[${t.name}]`))} ${t.message}`,r=[];return t.fix&&r.push(`${e.dim(`fix:`)} ${t.fix}`),t.sources?.length&&r.push(`${e.dim(`sources:`)} ${t.sources.join(`, `)}`),t.docs&&r.push(`${e.dim(`see:`)} ${e.cyan(t.docs)}`),r.length===0?n:[n,...r.map((t,n)=>`${e.dim(n{e=n,t=r}),resolve:e,reject:t}}var Ey=Math.random.bind(Math),Dy=`useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict`;function Oy(e=21){let t=``,n=e;for(;n--;)t+=Dy[Ey()*64|0];return t}var ky=6e4,Ay=e=>e,jy=Ay,{clearTimeout:My,setTimeout:Ny}=globalThis;function Py(e,t){let{post:n,on:r,off:i=()=>{},eventNames:a=[],serialize:o=Ay,deserialize:s=jy,resolver:c,bind:l=`rpc`,timeout:u=ky,proxify:d=!0}=t,f=!1,p=new Map,m,ee;async function h(e,r,i,a){if(f)throw Error(`[birpc] rpc is closed, cannot call "${e}"`);let s={m:e,a:r,t:`q`};a&&(s.o=!0);let c=async e=>n(o(e));if(i){await c(s);return}if(m)try{await m}finally{m=void 0}let{promise:l,resolve:d,reject:h}=Ty(),g=Oy();s.i=g;let te;async function ne(n=s){return u>=0&&(te=Ny(()=>{try{if(t.onTimeoutError?.call(ee,e,r)!==!0)throw Error(`[birpc] timeout on calling "${e}"`)}catch(e){h(e)}p.delete(g)},u),typeof te==`object`&&(te=te.unref?.())),p.set(g,{resolve:d,reject:h,timeoutId:te,method:e}),await c(n),l}try{t.onRequest?await t.onRequest.call(ee,s,ne,d):await ne()}catch(e){if(t.onGeneralError?.call(ee,e)!==!0)throw e;return}finally{My(te),p.delete(g)}return l}let g={$call:(e,...t)=>h(e,t,!1),$callOptional:(e,...t)=>h(e,t,!1,!0),$callEvent:(e,...t)=>h(e,t,!0),$callRaw:e=>h(e.method,e.args,e.event,e.optional),$rejectPendingCalls:ne,get $closed(){return f},get $meta(){return t.meta},$close:te,$functions:e};ee=d?new Proxy({},{get(t,n){if(Object.hasOwn(g,n))return g[n];if(n===`then`&&!a.includes(`then`)&&!(`then`in e))return;let r=(...e)=>h(n,e,!0);if(a.includes(n))return r.asEvent=r,r;let i=(...e)=>h(n,e,!1);return i.asEvent=r,i}}):g;function te(e){f=!0,p.forEach(({reject:t,method:n})=>{let r=Error(`[birpc] rpc is closed, cannot call "${n}"`);if(e)return e.cause??=r,t(e);t(r)}),p.clear(),i(re)}function ne(e){let t=Array.from(p.values()).map(({method:t,reject:n})=>e?e({method:t,reject:n}):n(Error(`[birpc]: rejected pending call "${t}".`)));return p.clear(),t}async function re(r,...i){let a;try{a=s(r)}catch(e){if(t.onGeneralError?.call(ee,e)!==!0)throw e;return}if(a.t===`q`){let{m:r,a:s,o:u}=a,d,f,p=await(c?c.call(ee,r,e[r]):e[r]);if(u&&(p||=()=>void 0),!p)f=Error(`[birpc] function "${r}" not found`);else try{d=await p.apply(l===`rpc`?ee:e,s)}catch(e){f=e}if(a.i){if(f&&t.onFunctionError&&t.onFunctionError.call(ee,f,r,s)===!0)return;if(!f)try{await n(o({t:`s`,i:a.i,r:d}),...i);return}catch(e){if(f=e,t.onGeneralError?.call(ee,e,r,s)!==!0)throw e}try{await n(o({t:`s`,i:a.i,e:f}),...i)}catch(e){if(t.onGeneralError?.call(ee,e,r,s)!==!0)throw e}}}else{let{i:e,r:t,e:n}=a,r=p.get(e);r&&(My(r.timeoutId),n?r.reject(n):r.resolve(t)),p.delete(e)}}return m=r(re),ee}function Fy(e,t){return t.safety?t.safety:e===`static`||e===`query`||e==null?`read`:`action`}var Iy=Object.freeze({type:`object`,additionalProperties:!0});function Ly(e){let t=e[`~standard`];if(t.jsonSchema)try{return t.jsonSchema.input({target:`draft-2020-12`})}catch{return Iy}return Iy}function Ry(e){if(!e||e.length===0)return{type:`object`,properties:{}};let t={},n=[];for(let r=0;rn[`arg${t}`]);if(`arg0`in n){let e=[];for(;`arg${e.length}`in n;)e.push(n[`arg${e.length}`]);return e}return Object.keys(n).length===0?[]:void 0}function By(e,t){return zy(e,t)??[e]}function Vy(e){return typeof e==`string`?`'${e}'`:new Gy().serialize(e)}var Hy=` _-,;:!?.'"()[]{}@*/\\&#%\`^+<=>|~$0123456789abcdefghijklmnopqrstuvwxyz`,Uy=(function(){let e=new Uint8Array(128);for(let t=0;t<69;t++)e[Hy.charCodeAt(t)]=t+1;for(let t=65;t<=90;t++)e[t]=e[t+32];return e})();function Wy(e,t){if(e===t)return 0;let n=Math.min(e.length,t.length),r=0;for(let i=0;ia?-1:1)}return e.length===t.length?r:e.lengththis.compare(e[0],t[0])),r=`${e}{`;for(let e=0;ethis.compare(e,t)))}`}$Map(e){return this.serializeObjectEntries(`Map`,e.entries())}}for(let t of[`Error`,`RegExp`,`URL`])e.prototype[`$`+t]=function(e){return`${t}(${e})`};for(let t of[`Int8Array`,`Uint8Array`,`Uint8ClampedArray`,`Int16Array`,`Uint16Array`,`Int32Array`,`Uint32Array`,`Float32Array`,`Float64Array`])e.prototype[`$`+t]=function(e){return`${t}[${e.join(`,`)}]`};for(let t of[`BigInt64Array`,`BigUint64Array`])e.prototype[`$`+t]=function(e){return`${t}[${e.join(`n,`)}${e.length>0?`n`:``}]`};return e})(),Ky=[1779033703,-1150833019,1013904242,-1521486534,1359893119,-1694144372,528734635,1541459225],qy=[1116352408,1899447441,-1245643825,-373957723,961987163,1508970993,-1841331548,-1424204075,-670586216,310598401,607225278,1426881987,1925078388,-2132889090,-1680079193,-1046744716,-459576895,-272742522,264347078,604807628,770255983,1249150122,1555081692,1996064986,-1740746414,-1473132947,-1341970488,-1084653625,-958395405,-710438585,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,-2117940946,-1838011259,-1564481375,-1474664885,-1035236496,-949202525,-778901479,-694614492,-200395387,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,-2067236844,-1933114872,-1866530822,-1538233109,-1090935817,-965641998],Jy=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_`,Yy=[],Xy=class{_data=new Zy;_hash=new Zy([...Ky]);_nDataBytes=0;_minBufferSize=0;finalize(e){e&&this._append(e);let t=this._nDataBytes*8,n=this._data.sigBytes*8;return this._data.words[n>>>5]|=128<<24-n%32,this._data.words[(n+64>>>9<<4)+14]=Math.floor(t/4294967296),this._data.words[(n+64>>>9<<4)+15]=t,this._data.sigBytes=this._data.words.length*4,this._process(),this._hash}_doProcessBlock(e,t){let n=this._hash.words,r=n[0],i=n[1],a=n[2],o=n[3],s=n[4],c=n[5],l=n[6],u=n[7];for(let n=0;n<64;n++){if(n<16)Yy[n]=e[t+n]|0;else{let e=Yy[n-15],t=(e<<25|e>>>7)^(e<<14|e>>>18)^e>>>3,r=Yy[n-2],i=(r<<15|r>>>17)^(r<<13|r>>>19)^r>>>10;Yy[n]=t+Yy[n-7]+i+Yy[n-16]}let d=s&c^~s&l,f=r&i^r&a^i&a,p=(r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22),m=(s<<26|s>>>6)^(s<<21|s>>>11)^(s<<7|s>>>25),ee=u+m+d+qy[n]+Yy[n],h=p+f;u=l,l=c,c=s,s=o+ee|0,o=a,a=i,i=r,r=ee+h|0}n[0]=n[0]+r|0,n[1]=n[1]+i|0,n[2]=n[2]+a|0,n[3]=n[3]+o|0,n[4]=n[4]+s|0,n[5]=n[5]+c|0,n[6]=n[6]+l|0,n[7]=n[7]+u|0}_append(e){typeof e==`string`&&(e=Zy.fromUtf8(e)),this._data.concat(e),this._nDataBytes+=e.sigBytes}_process(e){let t,n=this._data.sigBytes/64;n=e?Math.ceil(n):Math.max((n|0)-this._minBufferSize,0);let r=n*16,i=Math.min(r*4,this._data.sigBytes);if(r){for(let e=0;e>>2]|=(n.charCodeAt(e)&255)<<24-e%4*8;return new e(i,r)}toBase64(){let e=[];for(let t=0;t>>2]>>>24-t%4*8&255,r=this.words[t+1>>>2]>>>24-(t+1)%4*8&255,i=this.words[t+2>>>2]>>>24-(t+2)%4*8&255,a=n<<16|r<<8|i;for(let n=0;n<4&&t*8+n*6>>6*(3-n)&63))}return e.join(``)}concat(e){if(this.words[this.sigBytes>>>2]&=4294967295<<32-this.sigBytes%4*8,this.words.length=Math.ceil(this.sigBytes/4),this.sigBytes%4)for(let t=0;t>>2]>>>24-t%4*8&255;this.words[this.sigBytes+t>>>2]|=n<<24-(this.sigBytes+t)%4*8}else for(let t=0;t>>2]=e.words[t>>>2];this.sigBytes+=e.sigBytes}};function Qy(e){return new Xy().finalize(e).toBase64()}function $y(e){return Qy(Vy(e))}function eb(e){return $y(e)}function tb(){let e={};function t(t,...n){let r=e[t]||[];for(let e=0,t=r.length;e{e[t]=e[t]?.filter(e=>n!==e)}}function i(e,t){let n=r(e,((...e)=>(n(),t(...e))));return n}return{_listeners:e,emit:t,emitOnce:n,on:r,once:i}}var nb=/^[\w+.-]{2,}:\/\//;function rb(e){return e.endsWith(`/`)?e:`${e}/`}function ib(e){return(e.endsWith(`/`)?e.slice(0,-1):e)||`/`}function ab(e,...t){let n=e;for(let e of t)e&&e!==`/`&&(n=n?rb(n)+e.replace(/^\.?\//,``):e);return n}function ob(e,t){if(!t||t===`/`||nb.test(e))return e;let n=ib(t);return e.startsWith(n)?e:ab(n,e)}function sb(e,t){let n=e.match(nb);return t+(n?e.slice(n[0].length):e)}var cb=`useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict`;function lb(e=21){let t=``,n=e;for(;n--;)t+=cb[Math.random()*64|0];return t}var ub=Symbol.for(`immer-nothing`),db=Symbol.for(`immer-draftable`),fb=Symbol.for(`immer-state`),pb=[function(e){return`The plugin for '${e}' has not been loaded into Immer. To enable the plugin, import and call \`enable${e}()\` when initializing your application.`},function(e){return`produce can only be called on things that are draftable: plain objects, arrays, Map, Set or classes that are marked with '[immerable]: true'. Got '${e}'`},`This object has been frozen and should not be mutated`,function(e){return`Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? `+e},`An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.`,`Immer forbids circular references`,"The first or second argument to `produce` must be a function","The third argument to `produce` must be a function or undefined","First argument to `createDraft` must be a plain object, an array, or an immerable object","First argument to `finishDraft` must be a draft returned by `createDraft`",function(e){return`'current' expects a draft, got: ${e}`},`Object.defineProperty() cannot be used on an Immer draft`,`Object.setPrototypeOf() cannot be used on an Immer draft`,`Immer only supports deleting array indices`,`Immer only supports setting array indices and the 'length' property`,function(e){return`'original' expects a draft, got: ${e}`}];function mb(e,...t){{let n=pb[e],r=Rb(n)?n.apply(null,t):n;throw Error(`[Immer] ${r}`)}}var hb=Object,gb=hb.getPrototypeOf,_b=`constructor`,vb=`prototype`,yb=`configurable`,bb=`enumerable`,xb=`writable`,Sb=`value`,Cb=e=>!!e&&!!e[fb];function wb(e){return e?Db(e)||Pb(e)||!!e[db]||!!e[_b]?.[db]||Fb(e)||Ib(e):!1}var Tb=hb[vb][_b].toString(),Eb=new WeakMap;function Db(e){if(!e||!Lb(e))return!1;let t=gb(e);if(t===null||t===hb[vb])return!0;let n=hb.hasOwnProperty.call(t,_b)&&t[_b];if(n===Object)return!0;if(!Rb(n))return!1;let r=Eb.get(n);return r===void 0&&(r=Function.toString.call(n),Eb.set(n,r)),r===Tb}function Ob(e,t,n=!0){kb(e)===0?(n?Reflect.ownKeys(e):hb.keys(e)).forEach(n=>{t(n,e[n],e)}):e.forEach((n,r)=>t(r,n,e))}function kb(e){let t=e[fb];return t?t.type_:Pb(e)?1:Fb(e)?2:Ib(e)?3:0}var Ab=(e,t,n=kb(e))=>n===2?e.has(t):hb[vb].hasOwnProperty.call(e,t),jb=(e,t,n=kb(e))=>n===2?e.get(t):e[t],Mb=(e,t,n,r=kb(e))=>{r===2?e.set(t,n):r===3?e.add(n):e[t]=n};function Nb(e,t){return e===t?e!==0||1/e==1/t:e!==e&&t!==t}var Pb=Array.isArray,Fb=e=>e instanceof Map,Ib=e=>e instanceof Set,Lb=e=>typeof e==`object`,Rb=e=>typeof e==`function`,zb=e=>typeof e==`boolean`;function Bb(e){let t=+e;return Number.isInteger(t)&&String(t)===e}var Vb=e=>Lb(e)?e?.[fb]:null,Hb=e=>e.copy_||e.base_,Ub=e=>e.modified_?e.copy_:e.base_;function Wb(e,t){if(Fb(e))return new Map(e);if(Ib(e))return new Set(e);if(Pb(e))return Array[vb].slice.call(e);let n=Db(e);if(t===!0||t===`class_only`&&!n){let t=hb.getOwnPropertyDescriptors(e);delete t[fb];let n=Reflect.ownKeys(t);for(let r=0;r1&&hb.defineProperties(e,{set:qb,add:qb,clear:qb,delete:qb}),hb.freeze(e),t&&Ob(e,(e,t)=>{Gb(t,!0)},!1),e)}function Kb(){mb(2)}var qb={[Sb]:Kb};function Jb(e){return e===null||!Lb(e)||hb.isFrozen(e)}var Yb=`MapSet`,Xb=`Patches`,Zb=`ArrayMethods`,Qb={};function $b(e){let t=Qb[e];return t||mb(0,e),t}var ex=e=>!!Qb[e];function tx(e,t){Qb[e]||(Qb[e]=t)}var nx,rx=()=>nx,ix=(e,t)=>({drafts_:[],parent_:e,immer_:t,canAutoFreeze_:!0,unfinalizedDrafts_:0,handledSet_:new Set,processedForPatches_:new Set,mapSetPlugin_:ex(Yb)?$b(Yb):void 0,arrayMethodsPlugin_:ex(Zb)?$b(Zb):void 0});function ax(e,t){t&&(e.patchPlugin_=$b(Xb),e.patches_=[],e.inversePatches_=[],e.patchListener_=t)}function ox(e){sx(e),e.drafts_.forEach(lx),e.drafts_=null}function sx(e){e===nx&&(nx=e.parent_)}var cx=e=>nx=ix(nx,e);function lx(e){let t=e[fb];t.type_===0||t.type_===1?t.revoke_():t.revoked_=!0}function ux(e,t){t.unfinalizedDrafts_=t.drafts_.length;let n=t.drafts_[0];if(e!==void 0&&e!==n){n[fb].modified_&&(ox(t),mb(4)),wb(e)&&(e=dx(t,e));let{patchPlugin_:r}=t;r&&r.generateReplacementPatches_(n[fb].base_,e,t)}else e=dx(t,n);return fx(t,e,!0),ox(t),t.patches_&&t.patchListener_(t.patches_,t.inversePatches_),e===ub?void 0:e}function dx(e,t){if(Jb(t))return t;let n=t[fb];if(!n)return bx(t,e.handledSet_,e);if(!mx(n,e))return t;if(!n.modified_)return n.base_;if(!n.finalized_){let{callbacks_:t}=n;if(t)for(;t.length>0;)t.pop()(e);vx(n,e)}return n.copy_}function fx(e,t,n=!1){!e.parent_&&e.immer_.autoFreeze_&&e.canAutoFreeze_&&Gb(t,n)}function px(e){e.finalized_=!0,e.scope_.unfinalizedDrafts_--}var mx=(e,t)=>e.scope_===t,hx=[];function gx(e,t,n,r){let i=Hb(e),a=e.type_;if(r!==void 0&&jb(i,r,a)===t){Mb(i,r,n,a);return}if(!e.draftLocations_){let t=e.draftLocations_=new Map;Ob(i,(e,n)=>{if(Cb(n)){let r=t.get(n)||[];r.push(e),t.set(n,r)}})}let o=e.draftLocations_.get(t)??hx;for(let e of o)Mb(i,e,n,a)}function _x(e,t,n){e.callbacks_.push(function(r){let i=t;if(!i||!mx(i,r))return;r.mapSetPlugin_?.fixSetContents(i);let a=Ub(i);gx(e,i.draft_??i,a,n),vx(i,r)})}function vx(e,t){if(e.modified_&&!e.finalized_&&(e.type_===3||e.type_===1&&e.allIndicesReassigned_||(e.assigned_?.size??0)>0)){let{patchPlugin_:n}=t;if(n){let r=n.getPath(e);r&&n.generatePatches_(e,r,t)}px(e)}}function yx(e,t,n){let{scope_:r}=e;if(Cb(n)){let i=n[fb];mx(i,r)&&i.callbacks_.push(function(){kx(e),gx(e,n,Ub(i),t)})}else wb(n)&&e.callbacks_.push(function(){let i=Hb(e);e.type_===3?i.has(n)&&bx(n,r.handledSet_,r):jb(i,t,e.type_)===n&&r.drafts_.length>1&&(e.assigned_.get(t)??!1)===!0&&e.copy_&&bx(jb(e.copy_,t,e.type_),r.handledSet_,r)})}function bx(e,t,n){return!n.immer_.autoFreeze_&&n.unfinalizedDrafts_<1||Cb(e)||t.has(e)||!wb(e)||Jb(e)?e:(t.add(e),Ob(e,(r,i)=>{if(Cb(i)){let t=i[fb];mx(t,n)&&(Mb(e,r,Ub(t),e.type_),px(t))}else wb(i)&&bx(i,t,n)}),e)}function xx(e,t){let n=Pb(e),r={type_:+!!n,scope_:t?t.scope_:rx(),modified_:!1,finalized_:!1,assigned_:void 0,parent_:t,base_:e,draft_:null,copy_:null,revoke_:null,isManual_:!1,callbacks_:void 0},i=r,a=Sx;n&&(i=[r],a=Cx);let{revoke:o,proxy:s}=Proxy.revocable(i,a);return r.draft_=s,r.revoke_=o,[s,r]}var Sx={get(e,t){if(t===fb)return e;let n=e.scope_.arrayMethodsPlugin_,r=e.type_===1&&typeof t==`string`;if(r&&n?.isArrayOperationMethod(t))return n.createMethodInterceptor(e,t);let i=Hb(e);if(!Ab(i,t,e.type_))return Ex(e,i,t);let a=i[t];if(e.finalized_||!wb(a)||r&&e.operationMethod&&n?.isMutatingArrayMethod(e.operationMethod)&&Bb(t))return a;if(a===wx(e.base_,t)||Tx(e,t,a)){kx(e);let n=e.type_===1?+t:t,r=jx(e.scope_,a,e,n);return e.copy_[n]=r}return a},has(e,t){return t in Hb(e)},ownKeys(e){return Reflect.ownKeys(Hb(e))},set(e,t,n){let r=Dx(Hb(e),t);if(r?.set)return r.set.call(e.draft_,n),!0;if(!e.modified_){let r=wx(Hb(e),t),i=r?.[fb];if(i&&i.base_===n)return e.copy_[t]=n,e.assigned_.delete(t),!0;if(Nb(n,r)&&(n!==void 0||Ab(e.base_,t,e.type_)))return!0;kx(e),Ox(e)}return e.copy_[t]===n&&(n!==void 0||Ab(e.copy_,t,e.type_))||Number.isNaN(n)&&Number.isNaN(e.copy_[t])?!0:(e.copy_[t]=n,e.assigned_.set(t,!0),yx(e,t,n),!0)},deleteProperty(e,t){return kx(e),wx(e.base_,t)!==void 0||t in e.base_?(e.assigned_.set(t,!1),Ox(e)):e.assigned_.delete(t),e.copy_&&delete e.copy_[t],!0},getOwnPropertyDescriptor(e,t){let n=Hb(e),r=Reflect.getOwnPropertyDescriptor(n,t);return r&&{[xb]:!0,[yb]:e.type_!==1||t!==`length`,[bb]:r[bb],[Sb]:n[t]}},defineProperty(){mb(11)},getPrototypeOf(e){return gb(e.base_)},setPrototypeOf(){mb(12)}},Cx={};for(let e in Sx){let t=Sx[e];Cx[e]=function(){let e=arguments;return e[0]=e[0][0],t.apply(this,e)}}Cx.deleteProperty=function(e,t){return isNaN(parseInt(t))&&mb(13),Cx.set.call(this,e,t,void 0)},Cx.set=function(e,t,n){return t!==`length`&&isNaN(parseInt(t))&&mb(14),Sx.set.call(this,e[0],t,n,e[0])};function wx(e,t){let n=e[fb];return(n?Hb(n):e)[t]}function Tx(e,t,n){return e.type_!==1||!e.allIndicesReassigned_||e.assigned_?.get(t)||!wb(n)||n[fb]?!1:e.baseRefs_.has(n)}function Ex(e,t,n){let r=Dx(t,n);return r?Sb in r?r[Sb]:r.get?.call(e.draft_):void 0}function Dx(e,t){if(!(t in e))return;let n=gb(e);for(;n;){let e=Object.getOwnPropertyDescriptor(n,t);if(e)return e;n=gb(n)}}function Ox(e){e.modified_||(e.modified_=!0,e.parent_&&Ox(e.parent_))}function kx(e){e.copy_||=(e.assigned_=new Map,Wb(e.base_,e.scope_.immer_.useStrictShallowCopy_))}var Ax=class{constructor(e){this.autoFreeze_=!0,this.useStrictShallowCopy_=!1,this.useStrictIteration_=!1,this.produce=(e,t,n)=>{if(Rb(e)&&!Rb(t)){let n=t;t=e;let r=this;return function(e=n,...i){return r.produce(e,e=>t.call(this,e,...i))}}Rb(t)||mb(6),n!==void 0&&!Rb(n)&&mb(7);let r;if(wb(e)){let i=cx(this),a=jx(i,e,void 0),o=!0;try{r=t(a),o=!1}finally{o?ox(i):sx(i)}return ax(i,n),ux(r,i)}if(!e||!Lb(e)){if(r=t(e),r===void 0&&(r=e),r===ub&&(r=void 0),this.autoFreeze_&&Gb(r,!0),n){let t=[],i=[];$b(Xb).generateReplacementPatches_(e,r,{patches_:t,inversePatches_:i}),n(t,i)}return r}mb(1,e)},this.produceWithPatches=(e,t)=>{if(Rb(e))return(t,...n)=>this.produceWithPatches(t,t=>e(t,...n));let n,r;return[this.produce(e,t,(e,t)=>{n=e,r=t}),n,r]},zb(e?.autoFreeze)&&this.setAutoFreeze(e.autoFreeze),zb(e?.useStrictShallowCopy)&&this.setUseStrictShallowCopy(e.useStrictShallowCopy),zb(e?.useStrictIteration)&&this.setUseStrictIteration(e.useStrictIteration)}createDraft(e){wb(e)||mb(8),Cb(e)&&(e=Mx(e));let t=cx(this),n=jx(t,e,void 0);return n[fb].isManual_=!0,sx(t),n}finishDraft(e,t){let n=e&&e[fb];(!n||!n.isManual_)&&mb(9);let{scope_:r}=n;return ax(r,t),ux(void 0,r)}setAutoFreeze(e){this.autoFreeze_=e}setUseStrictShallowCopy(e){this.useStrictShallowCopy_=e}setUseStrictIteration(e){this.useStrictIteration_=e}shouldUseStrictIteration(){return this.useStrictIteration_}applyPatches(e,t){let n;for(n=t.length-1;n>=0;n--){let r=t[n];if(r.path.length===0&&r.op===`replace`){e=r.value;break}}n>-1&&(t=t.slice(n+1));let r=$b(Xb).applyPatches_;return Cb(e)?r(e,t):this.produce(e,e=>r(e,t))}};function jx(e,t,n,r){let[i,a]=Fb(t)?$b(Yb).proxyMap_(t,n):Ib(t)?$b(Yb).proxySet_(t,n):xx(t,n);return(n?.scope_??rx()).drafts_.push(i),a.callbacks_=n?.callbacks_??[],a.key_=r,n&&r!==void 0?_x(n,a,r):a.callbacks_.push(function(e){e.mapSetPlugin_?.fixSetContents(a);let{patchPlugin_:t}=e;a.modified_&&t&&t.generatePatches_(a,[],e)}),i}function Mx(e){return Cb(e)||mb(10,e),Nx(e)}function Nx(e){if(!wb(e)||Jb(e))return e;let t=e[fb],n,r=!0;if(t){if(!t.modified_)return t.base_;t.finalized_=!0,n=Wb(e,t.scope_.immer_.useStrictShallowCopy_),r=t.scope_.immer_.shouldUseStrictIteration()}else n=Wb(e,!0);let i=n;return Ib(n)&&(n=new Set),Ob(i,(e,t)=>{Mb(n,e,Nx(t))},r),t&&(t.finalized_=!1),n}function Px(){pb.push(`Sets cannot have "replace" patches.`,function(e){return`Unsupported patch operation: `+e},function(e){return`Cannot apply patch, path doesn't resolve: `+e},`Patching reserved attributes like __proto__, prototype and constructor is not allowed`);function e(n,r=[]){if(n.key_!==void 0){let e=n.parent_.copy_??n.parent_.base_,t=Vb(jb(e,n.key_)),i=jb(e,n.key_);if(i===void 0||i!==n.draft_&&i!==n.base_&&i!==n.copy_||t!=null&&t.base_!==n.base_)return null;let a=n.parent_.type_===3,o;if(a){let e=n.parent_;o=Array.from(e.drafts_.keys()).indexOf(n.key_)}else o=n.key_;if(!(a&&e.size>o||Ab(e,o))||n.parent_.type_===1&&n.parent_.allIndicesReassigned_)return null;r.push(o)}if(n.parent_)return e(n.parent_,r);r.reverse();try{t(n.copy_,r)}catch{return null}return r}function t(e,t){let n=e;for(let e=0;e{let u=jb(o,e,c),f=jb(s,e,c),p=l?Ab(o,e)?n:`add`:r;if(u===f&&p===n)return;let m=t.concat(e);i.push(p===r?{op:p,path:m}:{op:p,path:m,value:d(f)}),a.push(p===`add`?{op:r,path:m}:p===r?{op:`add`,path:m,value:d(u)}:{op:n,path:m,value:d(u)})})}function s(e,t,n,i){let{base_:a,copy_:o}=e,s=0;a.forEach(e=>{if(!o.has(e)){let a=t.concat([s]);n.push({op:r,path:a,value:e}),i.unshift({op:`add`,path:a,value:e})}s++}),s=0,o.forEach(e=>{if(!a.has(e)){let a=t.concat([s]);n.push({op:`add`,path:a,value:e}),i.unshift({op:r,path:a,value:e})}s++})}function c(e,t,r){let{patches_:i,inversePatches_:a}=r;i.push({op:n,path:[],value:t===ub?void 0:t}),a.push({op:n,path:[],value:e})}function l(e,t){return t.forEach(t=>{let{path:i,op:a}=t,o=e;for(let e=0;e[e,u(t)]));if(Ib(e))return new Set(Array.from(e).map(u));let t=Object.create(gb(e));for(let n in e)t[n]=u(e[n]);return Ab(e,db)&&(t[db]=e[db]),t}function d(e){return Cb(e)?u(e):e}tx(Xb,{applyPatches_:l,generatePatches_:i,generateReplacementPatches_:c,getPath:e})}globalThis.Iterator?.from;var Fx=new Ax,Ix=Fx.produce,Lx=Fx.produceWithPatches.bind(Fx),Rx=Fx.applyPatches.bind(Fx),zx=1e3;function Bx(e,t){if(e.add(t),e.size>zx){let t=e.values().next().value;t!==void 0&&e.delete(t)}}function Vx(e){let{enablePatches:t=!1}=e;t&&Px();let n=tb(),r=e.initialValue,i=new Set;return{on:n.on,value:()=>r,patch:(e,t=lb())=>{i.has(t)||(Px(),r=Rx(r,e),Bx(i,t),n.emit(`updated`,r,void 0,t))},mutate:(e,a=lb())=>{if(!i.has(a)){if(Bx(i,a),t){let[t,i]=Lx(r,e);if(t===r)return;r=t,n.emit(`updated`,r,i,a)}else{let t=Ix(r,e);if(t===r)return;r=t,n.emit(`updated`,r,void 0,a)}}},syncIds:i}}var Hx=typeof self==`object`?self:globalThis,Ux=new Set([`Error`,`EvalError`,`RangeError`,`ReferenceError`,`SyntaxError`,`TypeError`,`URIError`,`AggregateError`]),Wx=new Set([`Boolean`,`Number`,`String`,`Int8Array`,`Uint8Array`,`Uint8ClampedArray`,`Int16Array`,`Uint16Array`,`Int32Array`,`Uint32Array`,`Float16Array`,`Float32Array`,`Float64Array`,`BigInt64Array`,`BigUint64Array`]);function Gx(e,t){let n=(t,n)=>(e.set(n,t),t),r=i=>{if(e.has(i))return e.get(i);let[a,o]=t[i];switch(a){case 0:case-1:return n(o,i);case 1:{let e=n([],i);for(let t of o)e.push(r(t));return e}case 2:{let e=n({},i);for(let[t,n]of o)e[r(t)]=r(n);return e}case 3:return n(new Date(o),i);case 4:{let{source:e,flags:t}=o;return n(new RegExp(e,t),i)}case 5:{let e=n(new Map,i);for(let[t,n]of o)e.set(r(t),r(n));return e}case 6:{let e=n(new Set,i);for(let t of o)e.add(r(t));return e}case 7:{let{name:e,message:t}=o,r=Ux.has(e)?Hx[e]:void 0;return n(new(r??Hx.Error)(t),i)}case 8:return n(BigInt(o),i);case`BigInt`:return n(Object(BigInt(o)),i);case`ArrayBuffer`:return n(new Uint8Array(o).buffer,o);case`DataView`:{let{buffer:e}=new Uint8Array(o);return n(new DataView(e),o)}}if(typeof a==`string`&&Wx.has(a))return n(new Hx[a](o),i);throw TypeError(`unable to deserialize unsafe or unknown type: ${String(a)}`)};return r}function Kx(e){return Gx(new Map,e)(0)}var qx=``,{toString:Jx}={},{keys:Yx}=Object;function Xx(e){let t=typeof e;if(t!==`object`||!e)return[0,t];let n=Jx.call(e).slice(8,-1);switch(n){case`Array`:return[1,qx];case`Object`:return[2,qx];case`Date`:return[3,qx];case`RegExp`:return[4,qx];case`Map`:return[5,qx];case`Set`:return[6,qx];case`DataView`:return[1,n]}return n.includes(`Array`)?[1,n]:n.includes(`Error`)?[7,n]:[2,n]}function Zx([e,t]){return e===0&&(t===`function`||t===`symbol`)}function Qx(e,t,n,r){let i=(e,t)=>{let i=r.push(e)-1;return n.set(t,i),i},a=r=>{if(n.has(r))return n.get(r);let[o,s]=Xx(r);switch(o){case 0:{let t=r;switch(s){case`bigint`:o=8,t=r.toString();break;case`function`:case`symbol`:if(e)throw TypeError(`unable to serialize ${s}`);t=null;break;case`undefined`:return i([-1],r)}return i([o,t],r)}case 1:{if(s){let e=r;return s===`DataView`?e=new Uint8Array(r.buffer):s===`ArrayBuffer`&&(e=new Uint8Array(r)),i([s,[...e]],r)}let e=[],t=i([o,e],r);for(let t of r)e.push(a(t));return t}case 2:{if(s)switch(s){case`BigInt`:return i([s,r.toString()],r);case`Boolean`:case`Number`:case`String`:return i([s,r.valueOf()],r)}if(t&&`toJSON`in r)return a(r.toJSON());let n=[],c=i([o,n],r);for(let t of Yx(r))(e||!Zx(Xx(r[t])))&&n.push([a(t),a(r[t])]);return c}case 3:return i([o,r.toISOString()],r);case 4:{let{source:e,flags:t}=r;return i([o,{source:e,flags:t}],r)}case 5:{let t=[],n=i([o,t],r);for(let[n,i]of r)(e||!(Zx(Xx(n))||Zx(Xx(i))))&&t.push([a(n),a(i)]);return n}case 6:{let t=[],n=i([o,t],r);for(let n of r)(e||!Zx(Xx(n)))&&t.push(a(n));return n}}let{message:c}=r;return i([o,{name:s,message:c}],r)};return a}function $x(e,t={}){let n=[];return Qx(!(t.json||t.lossy),!!t.json,new Map,n)(e),n}var{parse:eS,stringify:tS}=JSON,nS={json:!0,lossy:!0};function rS(e){return Kx(eS(e))}function iS(e){return tS($x(e,nS))}function aS(e){return Kx(e)}function oS(e){return iS(e)}function sS(e){return rS(e)}var cS=256,lS=class extends Error{name=`StreamClosedError`};function uS(e={}){let t=e.id??lb(),n=Math.max(0,e.replayWindow??0),r=tb(),i=new AbortController,a=[],o=!1,s=0;function c(e){if(o)throw new lS(`Cannot write to a closed stream "${t}"`);s+=1,n>0&&(a.push({seq:s,chunk:e}),a.length>n&&(a.length-n===1?a.shift():a.splice(0,a.length-n))),r.emit(`chunk`,s,e)}function l(e){if(o)return;o=!0;let t=fS(e);i.abort(e),r.emit(`end`,t)}function u(){o||(o=!0,i.signal.aborted||i.abort(`stream closed`),r.emit(`end`,void 0))}function d(e){o||i.signal.aborted||i.abort(e??`aborted`)}let f=new WritableStream({write(e){c(e)},close(){u()},abort(e){l(e)}});return{id:t,signal:i.signal,get closed(){return o},get lastSeq(){return s},write:c,error:l,close:u,abort:d,writable:f,events:r,buffer:a}}function dS(e={}){let t=e.id??lb(),n=Math.max(1,e.highWaterMark??cS),r=[],i=0,a=!1,o=!1,s,c,l,u;function d(){if(c){if(r.length>0){let e=r.shift(),t=c;c=void 0,t.resolve({value:e,done:!1});return}if(a){let e=c;if(c=void 0,s){let t=Error(s.message);t.name=s.name,e.reject(t)}else e.resolve({value:void 0,done:!0})}}}function f(){if(l){for(;r.length>0;){let e=r.shift();try{l.enqueue(e)}catch{break}}if(a&&l){try{if(s){let e=Error(s.message);e.name=s.name,l.error(e)}else l.close()}catch{}l=void 0}}}function p(t,s){if(!(a||o)&&!(t<=i)){if(i=t,r.push(s),r.length>n){let t=r.length-n;r.splice(0,t),e.onOverflow?.(t)}d(),u&&f()}}function m(e){a||(a=!0,s=e,d(),u&&f())}function ee(){o||a||(o=!0,e.onCancel?.(),m(void 0))}function h(){return u||(u=new ReadableStream({start(e){l=e,f()},cancel(){ee()}}),u)}return{id:t,get cancelled(){return o},get done(){return a},get lastSeenSeq(){return i},get readable(){return h()},cancel:ee,_push:p,_end:m,[Symbol.asyncIterator](){return{next(){if(r.length>0)return Promise.resolve({value:r.shift(),done:!1});if(a){if(s){let e=Error(s.message);return e.name=s.name,Promise.reject(e)}return Promise.resolve({value:void 0,done:!0})}return new Promise((e,t)=>{c={resolve:e,reject:t}})},return(){return ee(),Promise.resolve({value:void 0,done:!0})}}}}}function fS(e){if(e instanceof Error)return{name:e.name||`Error`,message:e.message};if(typeof e==`string`)return{name:`Error`,message:e};try{return{name:`Error`,message:JSON.stringify(e)}}catch{return{name:`Error`,message:String(e)}}}var pS=128;function mS(e){return e.replace(/[^\w-]+/g,`_`).slice(0,pS)}var hS=`modulepreload`,gS=function(e,t){return new URL(e,t).href},_S={},vS=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=gS(t,n),t=s(t),t in _S)return;_S[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:hS,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}).filter(e=>e!==void 0))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},yS=`__connection.json`,bS=`__DEVFRAME_CONNECTION__`,xS=`x-birpc-session`,SS=`__rpc-dump/index.json`,CS=`devframe:services`,wS=`devframe_otp`,TS=`devframe_auth_token`;wy.postMessage.remoteAssetsError;var ES=class{cacheMap=new Map;options;keySerializer;constructor(e){this.options=e,this.keySerializer=e.keySerializer||(e=>eb(e))}updateOptions(e){this.options={...this.options,...e}}cached(e,t){let n=this.cacheMap.get(e);if(n)return n.get(this.keySerializer(t))}has(e,t){return this.cacheMap.get(e)?.has(this.keySerializer(t))??!1}apply(e,t){let n=this.cacheMap.get(e.m)||new Map;n.set(this.keySerializer(e.a),t),this.cacheMap.set(e.m,n)}validate(e){return this.options.functions.includes(e)}clear(e){e?this.cacheMap.delete(e):this.cacheMap.clear()}},DS=Mre({docsBase:`https://devfra.me/errors`,codes:{DF0019:{why:e=>`RPC function "${e.name}" has \`agent\` set but \`jsonSerializable\` is \`false\`; MCP requires JSON-serializable data.`,fix:"Remove `jsonSerializable: false`, or remove `agent` to keep it RPC-only."},DF0020:{why:e=>`RPC function "${e.name}" declares \`jsonSerializable: true\` but the value at "${e.path}" is a ${e.type}.`,fix:"Either drop `jsonSerializable: true` (falls back to structured-clone) or change the value to a JSON-safe shape."},DF0021:{why:e=>`RPC function "${e.name}" is already registered`,fix:"Use the `force` parameter to overwrite an existing registration."},DF0022:{why:e=>`RPC function "${e.name}" is not registered. Use register() to add new functions.`},DF0023:{why:e=>`RPC function "${e.name}" is not registered`},DF0024:{why:e=>`Either handler or setup function must be provided for RPC function "${e.name}"`},DF0025:{why:e=>`Function "${e.name}" not found in dump store`},DF0026:{why:e=>`No dump match for "${e.name}" with args: ${e.args}`},DF0027:{why:e=>`Function "${e.name}" with type "${e.type}" cannot have dump configuration. Only "static" and "query" types support dumps.`},DF0028:{why:e=>`Function "${e.name}" with type "${e.type}" cannot use \`snapshot: true\`. Only "query" functions support this sugar; "static" functions have equivalent default behavior already.`,fix:"Remove `snapshot: true`, or change the function type to `query`."},DF0043:{why:e=>`RPC function "${e.name}" received an invalid argument at position ${e.index}: ${e.issues}`,fix:"Pass a value that satisfies the `args` schema declared for this function."},DF0044:{why:e=>`RPC function "${e.name}" returned a value that failed its \`returns\` schema: ${e.issues}`,fix:"Make the handler return a value that satisfies the `returns` schema, or relax the schema."}}});function OS(e){if(e.agent&&e.jsonSerializable===!1)throw DS.DF0019({name:e.name});e.agent&&!e.jsonSerializable&&(e.jsonSerializable=!0)}async function kS(e,t){let n=e[`~standard`].validate(t);return n instanceof Promise?await n:n}function AS(e){return e.map(e=>{let t=e.path?.map(e=>typeof e==`object`?e.key:e).join(`.`);return t?`${t}: ${e.message}`:e.message}).join(`; `)}async function jS(e,t,n){let r=n.slice();if(!t||t.length===0)return r;for(let r=0;r{n.get(t)===r&&n.delete(t)}),n.set(t,r)),await r}if(!e.__promise){let n=Promise.resolve(e.setup(t));n.catch(()=>{e.__promise===n&&(e.__promise=void 0)}),e.__promise=n}return await e.__promise}async function PS(e,t){let n=e.handler;if(!n){let r=await NS(e,t);if(!r.handler)throw DS.DF0024({name:e.name});n=r.handler}let r=e.args,i=e.returns;if(!r&&!i)return n;let a=n;return async(...t)=>{let n=await jS(e.name,r,t),o=await a(...n);return await MS(e.name,i,o)}}var FS=class{context;definitions=new Map;functions;_onChanged=[];constructor(e){this.context=e;let t=this.definitions,n=this;this.functions=new Proxy({},{get(e,r){let i=t.get(r);if(i)return PS(i,n.context)},has(e,n){return t.has(n)},getOwnPropertyDescriptor(e,n){return{value:t.get(n)?.handler,configurable:!0,enumerable:!0}},ownKeys(){return Array.from(t.keys())}})}register(e,t=!1){if(this.definitions.has(e.name)&&!t)throw DS.DF0021({name:e.name});OS(e),this.definitions.set(e.name,e),this._onChanged.forEach(t=>t(e.name))}update(e,t=!1){if(!this.definitions.has(e.name)&&!t)throw DS.DF0022({name:e.name});OS(e),this.definitions.set(e.name,e),this._onChanged.forEach(t=>t(e.name))}onChanged(e){return this._onChanged.push(e),()=>{let t=this._onChanged.indexOf(e);t!==-1&&this._onChanged.splice(t,1)}}async getHandler(e){return await PS(this.definitions.get(e),this.context)}getSchema(e){let t=this.definitions.get(e);if(!t)throw DS.DF0023({name:String(e)});return{args:t.args,returns:t.returns}}has(e){return this.definitions.has(e)}get(e){return this.definitions.get(e)}list(){return Array.from(this.definitions.keys())}};function IS(e,t=``){return JSON.stringify(e,function(e,n){let r=this,i=r==null?n:r[e];if(i===void 0){if(Array.isArray(r))throw RS(t,`undefined`,r,e);return n}return i!==null&&LS(i,r,e,t),n})}function LS(e,t,n,r){if(typeof e==`bigint`)throw RS(r,`BigInt`,t,n);if(typeof e!=`object`)return;if(e instanceof Map)throw RS(r,`Map`,t,n);if(e instanceof Set)throw RS(r,`Set`,t,n);if(e instanceof Date)throw RS(r,`Date`,t,n);if(Array.isArray(e))return;let i=Object.getPrototypeOf(e);if(i!==null&&i!==Object.prototype)throw RS(r,e.constructor?.name??`class instance`,t,n)}function RS(e,t,n,r){let i=zS(n,r);return DS.DF0020({name:e||``,type:t,path:i})}function zS(e,t){return Array.isArray(e)?`[${t}]`:t===``?``:t}var BS=`__DEVFRAME_CONNECTION_META__`,VS=`__DEVFRAME_CONNECTION_AUTH_TOKEN__`;function HS(e){let t=[()=>window?.[e],()=>globalThis?.[e],()=>parent.window?.[e]];for(let e of t)try{let t=e();if(t)return t}catch{}}function US(){return HS(bS)}function WS(){return HS(BS)}function GS(e){if(e)return e;try{let e=localStorage.getItem(VS);if(e)return e}catch{}return HS(VS)}function KS(e){globalThis[bS]=e,globalThis[BS]={...e.connectionMeta,baseUrl:e.metaBaseUrl},e.authToken&&qS(e.authToken)}function qS(e){try{localStorage.setItem(VS,e)}catch{}globalThis[VS]=e;let t=US();t&&(globalThis[bS]={...t,authToken:e})}function JS(e){let t=ob(yS,e);try{return new URL(t,globalThis.location?.href).href}catch{return t}}function YS(e,t){return t&&t!==e.authToken?{...e,authToken:t}:e}function XS(){let e=US();if(e)return YS(e,GS()??e.authToken??e.connectionMeta.authToken);let t=WS();if(t)return{connectionMeta:t,metaBaseUrl:t.baseUrl??JS(`./`),authToken:GS(t.authToken)}}async function ZS(e={}){if(e.connection){let t=YS(e.connection,GS(e.authToken??e.connection.authToken??e.connection.connectionMeta.authToken));return KS(t),t}let t=Array.isArray(e.baseURL)?e.baseURL:[e.baseURL??`./`];if(e.connectionMeta){let n={connectionMeta:e.connectionMeta,metaBaseUrl:JS(t[0]??`./`),authToken:GS(e.authToken??e.connectionMeta.authToken)};return KS(n),n}let n=XS();if(n){let t=YS(n,GS(e.authToken??n.authToken??n.connectionMeta.authToken));return KS(t),t}let r=[];for(let n of t){let t=ob(yS,n),i=JS(n);try{let n=await fetch(t);if(!n.ok)throw Error(`Failed to fetch connection meta from ${i}: ${n.status}`);let r=await n.json(),a=n.url||i,o={connectionMeta:r,metaBaseUrl:r.baseUrl?new URL(r.baseUrl,a).href:a,authToken:GS(e.authToken??r.authToken)};return KS(o),o}catch(e){r.push(e)}}throw Error(`Failed to get connection meta from ${t.join(`, `)}`,{cause:r})}var QS=class extends Error{name=`DevframeConnectionError`;kind;constructor(e,t,n){super(t,n),this.kind=e}};function $S(e=wS){try{let t=globalThis.location?.hash?.replace(/^#/,``)??``;return new URLSearchParams(t).get(e)||void 0}catch{return}}function eC(e){try{let t=new URL(globalThis.location.href),n=new URLSearchParams(t.hash.replace(/^#/,``));if(!n.has(e))return;n.delete(e),t.hash=n.toString(),globalThis.history?.replaceState(globalThis.history.state,``,t.href)}catch{}}function tC(e=wS){let t=$S(e);return t&&eC(e),t}async function nC(e,t={}){let n=tC(t.param??`devframe_otp`);return n?e.isTrusted?!0:e.requestTrustWithCode(n):!1}function rC(e){let t={},n=new WeakMap,r,i=()=>(r??=e.sharedState.get(CS,{initialValue:{}}).then(e=>(t=e.value(),e.on(`updated`,e=>{t=e}),e)),r);return i(),{state:i,has:e=>e in t,keys:()=>Object.keys(t),get:r=>{let i=t[r];if(!i)return;let a=n.get(i);return a||(a={...i,rpc:e.scope(i.scope).rpc},n.set(i,a)),a}}}function iC(e){let t=new Map,n=new Map,r=new Map,i=new Set,a=e.connectionMeta.backend===`static`;function o(e,t){let n=r.get(e);return n&&typeof n==`object`&&!Array.isArray(n)&&typeof t==`object`&&!Array.isArray(t)?{...n,...t}:t}e.client.register({name:wy.broadcast.clientStateUpdated,type:`event`,handler:(e,n,r)=>{let i=t.get(e);i&&!i.syncIds.has(r)&&i.mutate(()=>o(e,n),r)}}),e.client.register({name:wy.broadcast.clientStatePatch,type:`event`,handler:(e,n,r)=>{let i=t.get(e);i&&!i.syncIds.has(r)&&i.patch(n,r)}});function s(t,n){let r=[];return r.push(n.on(`updated`,(n,r,i)=>{a||(r?e.callEvent(`devframe:rpc:server-state:patch`,t,r,i):e.callEvent(`devframe:rpc:server-state:set`,t,n,i))})),()=>{for(let e of r)e()}}return{keys:()=>Array.from(t.keys()),onKeyAdded(e){return i.add(e),()=>{i.delete(e)}},delete(e){let i=n.get(e);n.delete(e);let a=t.delete(e);return r.delete(e),i?.(),a},get:async(c,l)=>{if(l?.initialValue!==void 0&&r.set(c,l.initialValue),t.has(c))return t.get(c);let u=Vx({initialValue:l?.initialValue,enablePatches:!1});async function d(){if(a||e.callEvent(`devframe:rpc:server-state:subscribe`,c),l?.initialValue!==void 0){t.set(c,u);for(let e of i)e(c);return e.call(`devframe:rpc:server-state:get`,c).then(e=>{e!==void 0&&u.mutate(()=>o(c,e))}).catch(e=>{console.error(`Error getting server state`,e)}),n.set(c,s(c,u)),u}{let r=await e.call(`devframe:rpc:server-state:get`,c);u.mutate(()=>o(c,r)),t.set(c,u);for(let e of i)e(c);return n.set(c,s(c,u)),u}}return new Promise((t,n)=>{if(e.isTrusted)d().then(t,n);else{t(u);let n=!1;e.events.on(wy.client.isTrustedUpdated,e=>{e&&!n&&(n=!0,d())})}})}}}var aC=new Map;function oC(e=aC){let t=new Map;return{serialize:n=>{let r;return n.t===`q`?r=n.m:(r=t.get(n.i),t.delete(n.i)),!(n.t===`s`&&`e`in n)&&r&&e.get(r)?.jsonSerializable===!0?IS(n,r??``):`s:${oS(n)}`},deserialize:e=>{let n=e.startsWith(`s:`)?sS(e.slice(2)):JSON.parse(e);return n.t===`q`&&n.i&&n.m&&t.set(n.i,n.m),n}}}function sC(){}function cC(e){let t=e.search(/\n\n|\r\n\r\n/);if(!(t<0))return{frame:e.slice(0,t),rest:e.slice(t+(e[t]===`\r`?4:2))}}function lC(e){let t=`message`,n=[];for(let r of e.split(/\r?\n/))r.startsWith(`:`)||(r.startsWith(`event:`)?t=r.slice(6).trimStart():r.startsWith(`data:`)&&n.push(r.slice(5).replace(/^ /,``)));return{event:t,data:n}}function uC(e){let{onConnected:t=sC,onError:n=sC,onDisconnected:r=sC,definitions:i,fetch:a=globalThis.fetch.bind(globalThis)}=e,o=e.url;e.authToken&&(o=`${o}${o.includes(`?`)?`&`:`?`}${TS}=${encodeURIComponent(e.authToken)}`);let s=oC(i),c=new AbortController,l=!1,u,d,f,p,m=new Promise((e,t)=>{f=e,p=t});m.catch(()=>{});function ee(e){l||(l=!0,p(e),n(e),r())}function h(){l||(l=!0,p(Error(`Devframe SSE stream closed`)),r())}function g(e,n){if(e===`session`){f(n),t();return}u?.(n)}async function te(e){let t=e.getReader();d=t;let n=new TextDecoder,r=``;for(;;){let{done:e,value:i}=await t.read();if(e)break;for(r+=n.decode(i,{stream:!0});;){let e=cC(r);if(!e)break;r=e.rest;let{event:t,data:n}=lC(e.frame);n.length>0&&g(t,n.join(` +`))}}h()}return(async()=>{try{let e=await a(o,{headers:{accept:`text/event-stream`},signal:c.signal});if(!e.ok||!e.body)throw Error(`Devframe SSE stream request failed: ${e.status}`);await te(e.body)}catch(e){if(c.signal.aborted){h();return}ee(e instanceof Error?e:Error(String(e)))}})(),{close:()=>{l=!0,c.abort(),d?.cancel().catch(()=>{})},on:e=>{u=e},post:async e=>{let t;try{t=await m}catch{return}if(l){n(Error(`Devframe SSE channel is closed; message dropped`));return}try{let n=await a(o,{method:`POST`,headers:{"content-type":`text/plain; charset=utf-8`,[xS]:t},body:e});if(n.status===200){let e=await n.text();e&&u?.(e);return}if(!n.ok)throw Error(`Devframe SSE POST failed: ${n.status}`)}catch(e){n(e instanceof Error?e:Error(String(e)))}},serialize:s.serialize,deserialize:s.deserialize}}function dC(e,t){let{channel:n,rpcOptions:r={}}=t;return Py(e,{...n,timeout:-1,...r,proxify:!1})}function fC(e){let{transport:t,authToken:n,connectionMeta:r,events:i,clientRpc:a,rpcOptions:o={},callTimeout:s=0}=e,c=!1,l=`connecting`,u=null,d=Promise.withResolvers();function f(e,t=null){if(t?u=t:e===`connected`&&(u=null),e===l)return;let n=l;l=e,i.emit(wy.client.connectionStatus,e,n)}let p=new Set;function m(e){for(let t of[...p])t.reject(e)}function ee(){return l===`disconnected`||l===`error`?new QS(`connection`,`[devframe] Not connected to the devframe server`,{cause:u??void 0}):l===`unauthorized`?new QS(`auth`,`[devframe] Not authorized by the devframe server`,{cause:u??void 0}):null}function h(e,t){return new Promise((n,r)=>{let a=!1,o,c={reject(e){a||(l(),i.emit(wy.client.error,e,t),r(e))}};function l(){a=!0,p.delete(c),o&&clearTimeout(o)}p.add(c),s>0&&(o=setTimeout(()=>{c.reject(new QS(`timeout`,`[devframe] RPC call "${t}" timed out after ${s}ms`))},s)),e.then(e=>{a||(l(),n(e))},e=>{if(a)return;l();let n=e instanceof Error?e:Error(String(e));i.emit(wy.client.error,n,t),r(n)})})}let g=new Map;for(let e of r.jsonSerializableMethods??[])g.set(e,{jsonSerializable:!0});let te=e.createChannel({definitions:g,onError(e){f(`error`,e),i.emit(wy.client.connectionError,e),m(new QS(`connection`,`[devframe] Connection to the devframe server failed`,{cause:e}))},onDisconnected(){l!==`error`&&f(`disconnected`),m(new QS(`connection`,`[devframe] Disconnected from the devframe server`,{cause:u??void 0}))}}),ne=dC(a.functions,{channel:te,rpcOptions:o});a.register({name:wy.broadcast.authRevoked,type:`event`,handler:()=>{c=!1;let e=new QS(`auth`,`[devframe] The devframe server revoked this client's trust`);f(`unauthorized`,e),i.emit(wy.client.connectionError,e),m(e),i.emit(wy.client.isTrustedUpdated,!1)}});let re=n;async function ie(e){re=e;let t=await ne.$call(`anonymous:devframe:auth`,{authToken:e,ua:navigator.userAgent,origin:location.origin});if(c=t.isTrusted,c)d.resolve(!0),f(`connected`);else{let e=new QS(`auth`,`[devframe] The devframe server refused this client's credentials`);f(`unauthorized`,e),i.emit(wy.client.connectionError,e)}return i.emit(wy.client.isTrustedUpdated,c),t.isTrusted}async function ae(e){let t=(await ne.$call(`anonymous:devframe:auth:exchange`,{code:e,ua:navigator.userAgent,origin:location.origin}))?.authToken??null;return t&&(re=t,c=!0,d.resolve(!0),f(`connected`),i.emit(wy.client.isTrustedUpdated,!0)),t}async function oe(e={}){await ne.$call(`anonymous:devframe:auth:request-code`,{ua:navigator.userAgent,origin:location.origin,...e.reissue?{reissue:!0}:{}})}async function se(){return c?!0:ie(re??``)}async function ce(e=6e4){if(c&&d.resolve(!0),e<=0)return d.promise;let t;try{return await Promise.race([d.promise,new Promise((n,r)=>{t=setTimeout(()=>{r(Error(`[devframe] Timeout waiting for rpc to be trusted`))},e)})]),c}finally{clearTimeout(t)}}return{transport:t,get isTrusted(){return c},get status(){return l},get connectionError(){return u},requestTrust:se,requestTrustWithToken:ie,requestTrustWithCode:ae,requestAuthCode:oe,ensureTrusted:ce,call:(...e)=>{let t=String(e[0]),n=ee();return n?(i.emit(wy.client.error,n,t),Promise.reject(n)):h(ne.$call(...e),t)},callEvent:(...e)=>{let t=ee();if(t){i.emit(wy.client.error,t,String(e[0]));return}return ne.$callEvent(...e)},callOptional:(...e)=>{let t=String(e[0]),n=ee();return n?(i.emit(wy.client.error,n,t),Promise.reject(n)):h(ne.$callOptional(...e),t)},close:()=>{te.close()}}}function pC(e,t,n){let r=(()=>{try{return new URL(t,n.href)}catch{return new URL(n.href)}})();if(e&&typeof e==`object`){if(e.host!=null||e.port!=null){let t=e.host??`${r.hostname}:${e.port}`;return new URL(e.path??`/`,`${r.protocol}//${t}`).href}return new URL(e.path??``,r).href}let i=e??``;return/^https?:\/\//i.test(i)?i:new URL(i,r).href}function mC(e){let{authToken:t,connectionMeta:n,metaBaseUrl:r,events:i,clientRpc:a,rpcOptions:o={},sseOptions:s={},callTimeout:c=0}=e,l=pC(n.sse,r??`./`,location);return fC({transport:`sse`,authToken:t,connectionMeta:n,events:i,clientRpc:a,rpcOptions:o,callTimeout:c,createChannel:e=>uC({url:l,authToken:t,definitions:e.definitions,...s,onConnected(){s.onConnected?.()},onError(t){e.onError(t),s.onError?.(t)},onDisconnected(){e.onDisconnected(),s.onDisconnected?.()}})})}function hC(e){let{name:t,message:n,cause:r,...i}=e,a=r instanceof Error?r:gC(r)?hC(r):r,o=a===void 0?Error(n):Error(n,{cause:a});return o.name=t,Object.assign(o,i),o}function gC(e){return typeof e==`object`&&!!e&&typeof e.message==`string`&&typeof e.name==`string`}function _C(e){return typeof e==`object`&&!!e&&e.type===`static`&&typeof e.path==`string`}function vC(e){return typeof e==`object`&&!!e&&e.type===`query`&&typeof e.records==`object`&&e.records!==null}function yC(e){return typeof e==`object`&&!!e&&(`output`in e||`error`in e)}function bC(e){if(e.error)throw hC(e.error);return e.output}function xC(e){return e.some(e=>e!=null)}function SC(e){return typeof e==`object`&&e&&`serialization`in e&&`data`in e?e.data:e}function CC(e,t){let n=new Map,r=new Map;function i(e,t){return t===`structured-clone`&&Array.isArray(e)?aS(e):e}function a(e,t){return i(SC(e),t)}async function o(e){n.has(e.path)||n.set(e.path,t(e.path).then(t=>a(t,e.serialization)));let r=await n.get(e.path);return yC(r)?bC(r):r}async function s(e,n){return r.has(e)||r.set(e,t(e).then(e=>a(e,n))),await r.get(e)}async function c(t,n){if(!(t in e))throw Error(`[devframe-rpc] Function "${t}" not found in dump store`);let r=e[t];if(_C(r)){if(xC(n))throw Error(`[devframe-rpc] No dump match for "${t}" with args: ${JSON.stringify(n)}`);return await o(r)}if(vC(r)){let e=eb(n),i=r.records[e];if(i)return bC(await s(i,r.serialization));if(r.fallback)return bC(await s(r.fallback,r.serialization));throw Error(`[devframe-rpc] No dump match for "${t}" with args: ${JSON.stringify(n)}`)}if(!xC(n))return r;throw Error(`[devframe-rpc] No dump match for "${t}" with args: ${JSON.stringify(n)}`)}return{call:async(e,t)=>await c(e,t),callOptional:async(t,n)=>{if(t in e)return await c(t,n)},callEvent:async(e,t)=>{}}}async function wC(e){let t=CC(await e.fetchJsonFromBases(SS),e.fetchJsonFromBases);return{transport:`static`,isTrusted:!0,status:`connected`,connectionError:null,requestTrust:async()=>!0,requestTrustWithToken:async()=>!0,requestTrustWithCode:async()=>null,requestAuthCode:async()=>{},ensureTrusted:async()=>!0,call:(...e)=>t.call(e[0],e.slice(1)),callEvent:(...e)=>t.callEvent(e[0],e.slice(1)),callOptional:(...e)=>t.callOptional(e[0],e.slice(1)),close:()=>{}}}var TC=``;function EC(e,t){return`${e}${TC}${t}`}function DC(e){let t=new Map,n=new Map;e.client.register({name:wy.broadcast.streamingChunk,type:`event`,handler(e,n,r,i){t.get(EC(e,n))?._push(r,i)}}),e.client.register({name:wy.broadcast.streamingEnd,type:`event`,handler(e,n,r){let i=EC(e,n),a=t.get(i);a&&(a._end(r),t.delete(i))}}),e.client.register({name:wy.broadcast.streamingUploadCancel,type:`event`,handler(e,t){let r=EC(e,t),i=n.get(r);i&&(i.abort(`server cancelled upload`),n.delete(r))}}),e.events.on(wy.client.isTrustedUpdated,n=>{if(n)for(let[n,r]of t){if(r.cancelled||r.done)continue;let t=n.indexOf(TC);if(t<0)continue;let i=n.slice(0,t),a=n.slice(t+1);e.callEvent(`devframe:streaming:subscribe`,i,a,{afterSeq:r.lastSeenSeq})}});function r(n,r,i={}){let a=EC(n,r),o=t.get(a);if(o)return o;let s=dS({id:r,highWaterMark:i.highWaterMark,onOverflow(e){console.warn(`[devframe] DF0029: Stream "${n}#${r}" dropped ${e} chunk(s) after exceeding the client high-water mark.`)},onCancel(){e.callEvent(`devframe:streaming:cancel`,n,r),t.delete(a)}});if(t.set(a,s),e.isTrusted)e.callEvent(`devframe:streaming:subscribe`,n,r,{afterSeq:0});else{let i=e.events.on(wy.client.isTrustedUpdated,o=>{o&&(i(),t.has(a)&&!s.cancelled&&!s.done&&e.callEvent(`devframe:streaming:subscribe`,n,r,{afterSeq:s.lastSeenSeq}))})}return s}function i(t,r){let i=EC(t,r),a=n.get(i);if(a)return a;let o=uS({id:r});return o.events.on(`chunk`,(n,i)=>{e.callEvent(`devframe:streaming:upload-chunk`,t,r,n,i)}),o.events.on(`end`,a=>{e.callEvent(`devframe:streaming:upload-end`,t,r,a),n.delete(i)}),n.set(i,o),o}return{subscribe:r,upload:i}}function OC(){}var kC=new Map;function AC(e){let t=e.url;e.authToken&&(t=`${t}?${TS}=${encodeURIComponent(e.authToken)}`);let n=new WebSocket(t),{onConnected:r=OC,onError:i=OC,onDisconnected:a=OC,definitions:o=kC}=e;n.addEventListener(`open`,e=>{r(e)}),n.addEventListener(`error`,e=>{let t=e instanceof Error?e:Error(e.type);i(t)}),n.addEventListener(`close`,e=>{a(e)});let s=oC(o);return{close:()=>{n.close()},on:e=>{n.addEventListener(`message`,t=>{e(t.data)})},post:e=>{if(n.readyState===WebSocket.OPEN){n.send(e);return}if(n.readyState===WebSocket.CONNECTING){let t=()=>{i(),n.readyState===WebSocket.OPEN&&n.send(e)},r=()=>i();function i(){n.removeEventListener(`open`,t),n.removeEventListener(`close`,r)}n.addEventListener(`open`,t),n.addEventListener(`close`,r);return}i(Error(`Devframe WebSocket is not open; message dropped`))},serialize:s.serialize,deserialize:s.deserialize}}function jC(e,t,n){let r=(()=>{try{return new URL(t,n.href)}catch{return new URL(n.href)}})(),i=r.protocol===`https:`?`wss:`:`ws:`;if(e&&typeof e==`object`){if(e.host!=null||e.port!=null){let t=e.host??`${r.hostname}:${e.port}`,n=new URL(e.path??`/`,`${i}//${t}`);return n.protocol=i,n.href}let t=new URL(e.path??``,r);return t.protocol=i,t.href}if(typeof e==`number`)return`${i}//${r.hostname}:${e}`;let a=e??``;if(/^wss?:\/\//i.test(a))return a;if(/^https?:\/\//i.test(a))return sb(a,/^https/i.test(a)?`wss://`:`ws://`);let o=new URL(a,r);return o.protocol=i,o.href}function MC(e){let{authToken:t,connectionMeta:n,metaBaseUrl:r,events:i,clientRpc:a,rpcOptions:o={},wsOptions:s={},callTimeout:c=0}=e,l=jC(n.websocket,r??`./`,location);return fC({transport:`websocket`,authToken:t,connectionMeta:n,events:i,clientRpc:a,rpcOptions:o,callTimeout:c,createChannel:e=>AC({url:l,authToken:t,definitions:e.definitions,...s,onConnected(e){s.onConnected?.(e)},onError(t){e.onError(t),s.onError?.(t)},onDisconnected(t){e.onDisconnected(),s.onDisconnected?.(t)}})})}function NC(e){return e.includes(`:`)}function PC(e,t){return NC(t)?t:`${e}:${t}`}function FC(e){return{async get(t){return(await e()).value()?.[t]},async set(t,n){(await e()).mutate(e=>{if(e===void 0)return{[t]:n};e[t]=n})},async delete(t){(await e()).mutate(e=>{e!==void 0&&delete e[t]})},async all(){return(await e()).value()??{}},async onChange(t){return(await e()).on(`updated`,e=>t(e??{}))}}}function IC(e,t,n){let r=`devframe:settings:${n}:${t}`,i;function a(){return i||=(async()=>(await e.ensureTrusted(),e.sharedState.get(r)))().catch(e=>{throw i=void 0,e}),i}return FC(a)}function LC(e,t){return{global:IC(e,t,`global`),project:IC(e,t,`project`)}}function RC(e,t){return{namespace:t,base:e,rpc:{namespace:t,register(n){if(NC(n.name))throw Error(`[devframe] Scoped client RPC registration for namespace "${t}" received an already-namespaced function name "${n.name}". Pass a bare name without a ":" separator.`);e.client.register({...n,name:`${t}:${n.name}`})},call:((n,...r)=>e.call(PC(t,n),...r)),callEvent:((n,...r)=>e.callEvent(PC(t,n),...r)),callOptional:((n,...r)=>e.callOptional(PC(t,n),...r)),sharedState:((n,r)=>e.sharedState.get(PC(t,n),r)),streaming:{subscribe:(n,r,i)=>e.streaming.subscribe(PC(t,n),r,i),upload:(n,r)=>e.streaming.upload(PC(t,n),r)}},settings:LC(e,t),scope:e.scope}}function zC(){if(typeof document<`u`){let e=document.modelContext;if(e)return e}if(typeof navigator<`u`){let e=navigator.modelContext;if(e)return e}}function BC(e,t={}){let n=t.modelContext??zC();if(!n)return()=>{};let r=n,i=new Map,a=new Map;function o(t,n){let o=mS(t.name),s=a.get(o);if(s&&s!==t.name){console.warn(`[devframe] WebMCP tool name "${o}" (from "${t.name}") collides with "${s}"; keeping the first registration.`);return}let c=new AbortController,l=Fy(t.type,n),u=r.registerTool({name:o,description:n.description,inputSchema:Ry(t.args),annotations:{title:n.title??t.name,readOnlyHint:l===`read`,destructiveHint:l===`destructive`},execute:n=>VC(t,e.context,n)},{signal:c.signal});u&&`then`in u&&u.then(()=>{},()=>{}),a.set(o,t.name),i.set(t.name,()=>{c.abort(),u&&`unregister`in u&&typeof u.unregister==`function`&&u.unregister(),a.delete(o)})}function s(t){let n=t?[t]:[...e.definitions.keys()];for(let t of n){i.get(t)?.(),i.delete(t);let n=e.definitions.get(t),r=n?.agent;n&&r&&o(n,r)}}s();let c=e.onChanged(e=>s(e));return()=>{c();for(let e of i.values())e();i.clear()}}async function VC(e,t,n){try{let r=By(n,e.args?.length);return{content:[{type:`text`,text:HC(await(await PS(e,t))(...r))}]}}catch(e){return{isError:!0,content:[{type:`text`,text:UC(e)}]}}}function HC(e){return e===void 0?`undefined`:typeof e==`string`?e:JSON.stringify(e,null,2)}function UC(e){if(!(e instanceof Error))return String(e);let t=e.cause instanceof Error?` (cause: ${e.cause.message})`:``;return`${e.name}: ${e.message}${t}`}function WC(e,t){if(t.backend===`static`)return`static`;let n=t.websocket!==void 0,r=t.sse!==void 0;if(e===`websocket`){if(!n)throw Error(`[devframe] transport: 'websocket' was requested, but this server does not advertise a WebSocket endpoint`);return`websocket`}if(e===`sse`){if(!r)throw Error(`[devframe] transport: 'sse' was requested, but this server does not advertise an SSE endpoint`);return`sse`}if(t.backend===`sse`&&r)return`sse`;if(n)return`websocket`;if(r)return`sse`;throw Error(`[devframe] This server advertises no RPC transport (backend "none"), so there is nothing to connect to. Enable the WebSocket or SSE endpoint on the server, or use its static/MCP surfaces instead.`)}async function GC(e={}){let{baseURL:t=`./`,rpcOptions:n={},cacheOptions:r=!1}=e,i=tb(),a=Array.isArray(t)?t:[t],o=await ZS(e),{connectionMeta:s,metaBaseUrl:c,authToken:l}=o,u=a[0]??`./`;try{u=new URL(`.`,c).href}catch{}let d=new ES({functions:[],...typeof e.cacheOptions==`object`?e.cacheOptions:{}}),f={rpc:void 0},p=new FS(f),m=e.webmcp===!1?void 0:BC(p),ee,h=!1,g=0,te;function ne(){g++,te=void 0,d.clear(),r===!0&&d.updateOptions({functions:[]})}r&&(p.register({name:wy.broadcast.cacheInvalidate,type:`event`,handler:ne}),i.on(wy.client.connectionStatus,ne),i.on(wy.client.isTrustedUpdated,ne));async function re(e){let t=[u,...a.filter(e=>e!==u)].filter(e=>e!=null),n=[];for(let r of t)try{return await fetch(ob(e,r)).then(t=>{if(!t.ok)throw Error(`Failed to fetch ${e} from ${r}: ${t.status}`);return t.json()})}catch(e){n.push(e)}throw Error(`Failed to load ${e} from ${t.join(`, `)}`,{cause:n})}let ie=e.callTimeout??0,ae=ie>0?ie:1/0,oe,se={authToken:l,connectionMeta:s,metaBaseUrl:c,events:i,clientRpc:p,callTimeout:e.callTimeout,rpcOptions:{...n,async onRequest(e,t,i){let a=performance.now()+ae;if(await n.onRequest?.call(this,e,t,i),r===!0&&e.i&&oe.isTrusted&&!e.m.startsWith(`anonymous:`)&&e.m!==`devframe:rpc:cacheable-functions`){function t(){if(performance.now()>=a)throw new QS(`timeout`,`[devframe] RPC call "${e.m}" timed out after ${ie}ms`)}for(;!te&&(t(),!(h||oe.status!==`connected`));){let e=g;te=oe.callOptional(`devframe:rpc:cacheable-functions`).then(t=>{e===g&&d.updateOptions({functions:t??[]})}).catch(t=>{throw e===g&&(te=void 0),t}),await te}await te,t()}let o=g;if(r&&e.i&&!h&&oe.isTrusted&&d.validate(e.m)){if(d.has(e.m,e.a))return i(d.cached(e.m,e.a));let n=await t(e);o===g&&!h&&d.apply(e,n)}else await t(e)}}},ce=WC(e.transport??`auto`,s);oe=ce===`static`?await wC({fetchJsonFromBases:re}):ce===`sse`?mC({...se,sseOptions:e.sseOptions}):MC({...se,wsOptions:e.wsOptions});let le;try{le=new BroadcastChannel(`devframe-auth`)}catch{}let ue,de=!1;function fe(e){return((...t)=>de||!ue?e(...t):ue.then(()=>e(...t)))}function pe(){h=!0,ne();try{ee?.(),m?.()}finally{try{le?.close()}finally{oe.close?.()}}}let me={events:i,get isTrusted(){return oe.isTrusted},get status(){return oe.status},get connectionError(){return oe.connectionError},get transport(){return oe.transport??ce},get connection(){return o},connectionMeta:s,ensureTrusted:oe.ensureTrusted,requestTrust:oe.requestTrust,requestTrustWithToken:async e=>(qS(e),o={...o,authToken:e},oe.requestTrustWithToken(e)),requestTrustWithCode:async e=>{let t=await oe.requestTrustWithCode(e);if(!t)return!1;qS(t),o={...o,authToken:t};try{le?.postMessage({type:`auth-update`,authToken:t})}catch{}return!0},requestAuthCode:e=>oe.requestAuthCode(e),call:fe(oe.call),callEvent:fe(oe.callEvent),callOptional:fe(oe.callOptional),client:p,sharedState:void 0,services:void 0,streaming:void 0,cacheManager:d,scope:void 0,close:pe};me.sharedState=iC(me),me.streaming=DC(me),me.services=rC(me);let he=new Map;me.scope=(e=>{if(!e)return me;let t=he.get(e);return t||(t=RC(me,e),he.set(e,t)),t}),f.rpc=me;function ge(){try{return typeof window<`u`&&window.self===window.top}catch{return!1}}async function _e(){if(e.simpleAuth!==!1&&ge()&&typeof globalThis.prompt==`function`)for(await me.requestAuthCode().catch(()=>{});!me.isTrusted;){let e=globalThis.prompt(`devframe: enter the authentication code shown in your terminal`);if(e==null)return;let t=e.trim();if(t&&await me.requestTrustWithCode(t))return}}async function ve(){let t=await oe.requestTrust(),n=e.otpParam??`devframe_otp`,r=n?await nC(me,{param:n}):!1;t||r||me.isTrusted||await _e()}return ue=ve().then(()=>{de=!0},()=>{de=!0}),s.mcp&&vS(async()=>{let{setupBrowserAgentRpcBridge:e}=await import(`./browser-agent-rpc-KH4HHvHf-DqNFcBHr.js`);return{setupBrowserAgentRpcBridge:e}},[],import.meta.url).then(({setupBrowserAgentRpcBridge:e})=>{h||(ee=e(me))}).catch(()=>{}),le&&(le.onmessage=e=>{e.data?.type===`auth-update`&&e.data.authToken&&me.requestTrustWithToken(e.data.authToken)}),me}var KC=GC,qC=[`components`,`injectors`,`signals`,`ngrx`,`tanstackQuery`,`forms`,`router`,`pipes`,`http`,`analog`,`capacitor`,`errors`,`plugins`,`performance`,`build`],JC=[`angular`,`ngrx`,`analog`,`angular-native`,`nativescript`,`capacitor`];function YC(e){return`pangular:${e}`}var XC=[`forms`,`router`,`ngrx`,`http`,`analog`,`components`,`tanstackQuery`,`plugins`],ZC={refreshMs:{default:3e3,min:500,max:8e3},navigations:{default:50,min:5,max:500},formTimeline:{default:200,min:10,max:2e3},httpCalls:{default:200,min:10,max:2e3},changeLog:{default:200,min:10,max:2e3},cdCycles:{default:200,min:10,max:2e3},cdSlowMs:{default:16,min:1,max:1e3},capacitorCalls:{default:200,min:10,max:2e3},idleAfterMs:{default:6e4,min:0,max:6e5}},QC={"opted-out":`the request sets transferCache: false`,post:`POST requests are left out unless includePostRequests is set`,method:`only GET, HEAD and POST requests are cached`,"auth-headers":`it sends an Authorization, Proxy-Authorization or Cookie header`,credentials:`it sends credentials (withCredentials or credentials: include/same-origin)`,"no-cache-request":`the request asks for no-store, no-cache or private`,"no-cache-response":`the response has Cache-Control no-store, no-cache or private`,"set-cookie":`the response sets a cookie`,error:`failed responses are not stored`,mocked:`a fault rule answered before the transfer cache ran`,filter:`the transferCache filter passed to withPangular() left it out`,"cache-off-or-filter":`the transfer cache is off, or its filter (or another withHttpTransferCacheOptions setting) left it out`},$C=[[0,`Network error (offline)`],[200,`OK`],[201,`Created`],[202,`Accepted`],[203,`Non-Authoritative Information`],[204,`No Content`],[205,`Reset Content`],[206,`Partial Content`],[207,`Multi-Status`],[208,`Already Reported`],[226,`IM Used`],[300,`Multiple Choices`],[301,`Moved Permanently`],[302,`Found`],[303,`See Other`],[304,`Not Modified`],[307,`Temporary Redirect`],[308,`Permanent Redirect`],[400,`Bad Request`],[401,`Unauthorized`],[402,`Payment Required`],[403,`Forbidden`],[404,`Not Found`],[405,`Method Not Allowed`],[406,`Not Acceptable`],[407,`Proxy Authentication Required`],[408,`Request Timeout`],[409,`Conflict`],[410,`Gone`],[411,`Length Required`],[412,`Precondition Failed`],[413,`Content Too Large`],[414,`URI Too Long`],[415,`Unsupported Media Type`],[416,`Range Not Satisfiable`],[417,`Expectation Failed`],[418,`I'm a Teapot`],[421,`Misdirected Request`],[422,`Unprocessable Content`],[423,`Locked`],[424,`Failed Dependency`],[425,`Too Early`],[426,`Upgrade Required`],[428,`Precondition Required`],[429,`Too Many Requests`],[431,`Request Header Fields Too Large`],[451,`Unavailable For Legal Reasons`],[499,`Client Closed Request`],[500,`Internal Server Error`],[501,`Not Implemented`],[502,`Bad Gateway`],[503,`Service Unavailable`],[504,`Gateway Timeout`],[505,`HTTP Version Not Supported`],[506,`Variant Also Negotiates`],[507,`Insufficient Storage`],[508,`Loop Detected`],[510,`Not Extended`],[511,`Network Authentication Required`],[520,`Web Server Returned an Unknown Error`],[521,`Web Server Is Down`],[522,`Connection Timed Out`],[523,`Origin Is Unreachable`],[524,`A Timeout Occurred`]];function ew(e){return $C.some(([t])=>t===e)}var tw=/^[a-z][a-z0-9-]{0,39}$/;function nw(e,t){let n=t.plugins?.[e],r=t.inspectors.plugins&&tw.test(e)&&(n?.enabled??!0);return{enabled:r,tools:r&&t.agent.tools.plugins&&(n?.tools??!0),actions:r&&t.actions.plugins&&(n?.actions??!0)}}var rw=`pangular`,iw={mod:`Mod`,ctrl:`Ctrl`,control:`Ctrl`,meta:`Meta`,cmd:`Meta`,command:`Meta`,alt:`Alt`,option:`Alt`,opt:`Alt`,shift:`Shift`},aw=[`Mod`,`Ctrl`,`Meta`,`Alt`,`Shift`];function ow(e){if(typeof e!=`string`||e.length>40)return;let t=e.split(`+`).map(e=>e.trim());if(t.some(e=>!e))return;let n=new Set,r;for(let e of t){let t=iw[e.toLowerCase()];if(t){if(n.has(t))return;n.add(t);continue}if(r!==void 0)return;if(/^[a-z0-9]$/i.test(e))r=e.toUpperCase();else if(/^f([1-9]|1[0-2])$/i.test(e))r=e.toUpperCase();else return}if(r&&[...n].some(e=>e!==`Shift`)&&!(n.has(`Mod`)&&(n.has(`Ctrl`)||n.has(`Meta`))))return[...aw.filter(e=>n.has(e)),r].join(`+`)}function sw(e,t,n){let r=t.split(`+`),i=r.pop(),a=new Set(r),o=a.has(`Ctrl`)||a.has(`Mod`)&&!n,s=a.has(`Meta`)||a.has(`Mod`)&&n;if(e.ctrlKey!==o||e.metaKey!==s||e.altKey!==a.has(`Alt`)||e.shiftKey!==a.has(`Shift`))return!1;if(/^F\d+$/.test(i))return e.key.toUpperCase()===i;let c=/^\d$/.test(i)?`Digit${i}`:`Key${i}`;return e.code?e.code===c:e.key.toUpperCase()===i}function cw(e,t){if(!t)return e.replace(/^Mod\b/,`Ctrl`);let n={Mod:`⌘`,Meta:`⌘`,Ctrl:`⌃`,Alt:`⌥`,Shift:`⇧`};return e.split(`+`).map(e=>n[e]??e).join(``)}var lw=[{id:`dashboard`,label:`Dashboard`},{id:`timeline`,label:`Timeline`},{id:`audit`,label:`Audit`},{id:`analog`,label:`Analog`,inspector:`analog`},{id:`components`,label:`Components`,inspector:`components`},{id:`routes`,label:`Routes`,inspector:`router`},{id:`signals`,label:`Signals`,inspector:`signals`},{id:`injectors`,label:`Injectors`,inspector:`injectors`},{id:`store`,label:`Store`,inspector:`ngrx`},{id:`queries`,label:`Queries`,inspector:`tanstackQuery`},{id:`forms`,label:`Forms`,inspector:`forms`},{id:`pipes`,label:`Pipes`,inspector:`pipes`},{id:`network`,label:`SSR & HTTP`,inspector:`http`},{id:`errors`,label:`Errors`,inspector:`errors`},{id:`performance`,label:`Performance`,inspector:`performance`},{id:`build`,label:`Build`,inspector:`build`},{id:`capacitor`,label:`Capacitor`,inspector:`capacitor`},{id:`plugins`,label:`Plugins`,inspector:`plugins`}],uw=1e4;function dw(e){return lw.filter(t=>!(`inspector`in t)||e.inspectors[t.inspector])}var fw={components:`a component instance, by its id in the component tree`,injectors:`a component's element injector, by the component's id`,signals:`a component's signal graph, by the component's id`,forms:`a form, by its id`},pw=200;function mw(e){let t=new URLSearchParams({tab:e.tab});return e.id&&t.set(`id`,e.id),e.page&&t.set(`page`,e.page),`#${t.toString()}`}function hw(e){let t=new URLSearchParams(e.replace(/^#/,``)),n=e=>{let n=t.get(e)?.trim();return n&&n.length<=pw?n:void 0},r=n(`tab`);if(!r)return null;let i=n(`id`),a=n(`page`);return{tab:r,...i?{id:i}:{},...a?{page:a}:{}}}var gw=[{id:`settings`,title:`Open settings`,group:`Go to`,keywords:`preferences tabs`},{id:`pick-component`,title:`Pick component on page`,group:`Actions`,keywords:`select inspect element`,inspector:`components`},{id:`cd-record-start`,title:`Start recording change detection`,group:`Actions`,keywords:`profiler cycles`,inspector:`components`},{id:`cd-record-stop`,title:`Stop recording change detection`,group:`Actions`,keywords:`profiler cycles`,inspector:`components`},{id:`cd-clear`,title:`Clear change detection recording`,group:`Actions`,keywords:`reset recordings cycles`,inspector:`components`},{id:`theme:system`,title:`Theme: follow the system`,group:`Theme`,keywords:`auto`},{id:`theme:light`,title:`Theme: light`,group:`Theme`},{id:`theme:dark`,title:`Theme: dark`,group:`Theme`},{id:`dock:float`,title:`Popup: float`,group:`Popup`,keywords:`dock`,popupOnly:!0},{id:`dock:bottom`,title:`Popup: dock to the bottom`,group:`Popup`,keywords:`dock`,popupOnly:!0},{id:`dock:right`,title:`Popup: dock to the right`,group:`Popup`,keywords:`dock`,popupOnly:!0},{id:`dock:reset-size`,title:`Popup: reset the docked size`,group:`Popup`,keywords:`splitter resize height width`,popupOnly:!0},{id:`popout`,title:`Popup: open in its own window`,group:`Popup`,keywords:`pop out detach new window`,popupOnly:!0},{id:`popout:return`,title:`Popup: back into the page`,group:`Popup`,keywords:`pop in attach dock window`,popupOnly:!0}];function _w(e){let t=dw(e).map(e=>({id:`tab:${e.id}`,title:`Go to ${e.label}`,group:`Go to`,keywords:`tab open`})),n=gw.filter(t=>!t.inspector||e.inspectors[t.inspector]).map(({inspector:e,...t})=>t);return[...t,...n]}function vw(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:{}}var yw=Object.freeze({minLength:24,threshold:4.5}),bw={min:16,max:512},xw={min:2,max:6};function Sw(e){return typeof e==`number`&&Number.isInteger(e)&&e>=bw.min&&e<=bw.max}function Cw(e){return typeof e==`number`&&Number.isFinite(e)&&e>=xw.min&&e<=xw.max}function ww(e){if(e===!0)return{...yw};if(!e||typeof e!=`object`||Array.isArray(e))return;let{minLength:t,threshold:n}=e;return{minLength:Sw(t)?t:yw.minLength,threshold:Cw(n)?n:yw.threshold}}function Tw(e){let t=ww(e);return t?{entropy:t}:{}}function Ew(e,t){return typeof e==`boolean`?e:t}var Dw=100,Ow=100;function kw(e){if(!Array.isArray(e))return[];let t=e.filter(e=>typeof e==`string`).map(e=>e.trim()).filter(e=>e&&e.length<=Ow);return[...new Set(t)].slice(0,Dw)}function Aw(e,t){let{default:n,min:r,max:i}=ZC[t];return typeof e!=`number`||!Number.isFinite(e)?n:Math.min(i,Math.max(r,Math.round(e)))}function jw(e,t){return Object.fromEntries(e.map(e=>[e,t(e)]))}function Mw(e){let t=vw(e),n=vw(t.agent),r=vw(t.inspectors),i=vw(n.tools),a=vw(t.actions),o=Ew(t.actions,!0),s=vw(t.redaction),c=vw(t.limits),l=jw(qC,e=>Ew(r[e],!0));return{inspectors:l,agent:{readOnly:Ew(n.readOnly,!1),tools:jw(qC,e=>l[e]&&Ew(i[e],!0)),browserTools:Ew(n.browserTools,!1)},actions:jw(XC,e=>l[e]&&Ew(a[e],o)),redaction:{secretNames:kw(s.secretNames),unmask:kw(s.unmask),...Tw(s.entropy)},limits:Object.fromEntries(Object.keys(ZC).map(e=>[e,Aw(c[e],e)])),popup:{hotkey:zw(vw(t.popup).hotkey)},plugins:Rw(t.plugins),lint:{rules:Iw(t.lint)},analog:Pw(t.analog),ngrx:{trace:Ew(vw(t.ngrx).trace,!1)}}}var Nw=500;function Pw(e){let t=vw(e).contentSchema;return typeof t==`string`&&t.trim()&&t.length<=Nw?{contentSchema:t.trim()}:{}}var Fw=200;function Iw(e){let t=vw(vw(e).rules),n={};for(let[e,r]of Object.entries(t).slice(0,Fw))typeof r==`boolean`&&(n[e]=r);return n}var Lw=100;function Rw(e){let t={};for(let[n,r]of Object.entries(vw(e)).slice(0,Lw)){if(!tw.test(n))continue;if(typeof r==`boolean`){t[n]={enabled:r,tools:!0,actions:!0};continue}let e=vw(r);r!==void 0&&Object.keys(e).length&&(t[n]={enabled:Ew(e.enabled,!0),tools:Ew(e.tools,!0),actions:Ew(e.actions,!0)})}return t}function zw(e){return e===!1?!1:ow(e)??`Alt+Shift+P`}function Bw(e){let t=e?.configs;return Mw(t?.[rw])}function Vw(e){return`${{forms:`Writing to forms`,router:`Navigating and clearing navigations`,ngrx:`Restoring NgRx state, dispatching actions and changing the Store DevTools history`,http:`Changing HTTP mock rules and SSR overrides, refetching the TransferState payload, and clearing HTTP calls`,analog:`Calling API routes, clearing server calls, running Nitro tasks and purging the Nitro cache`,components:`Editing component properties and signals`,tanstackQuery:`Invalidating and refetching TanStack queries`,plugins:`Running plugin actions`}[e]} is turned off in the devtools config (actions.${e}).`}var Hw=[`pangular:build-meta`],Uw={components:`Components`,injectors:`Injectors`,signals:`Signals`,ngrx:`NgRx`,tanstackQuery:`TanStack Query`,forms:`Forms`,router:`Router`,pipes:`Pipes`,http:`HTTP`,analog:`Analog`,capacitor:`Capacitor`,errors:`Errors`,plugins:`Plugins`,performance:`Performance`,build:`Build`},Ww={forms:`Form writes`,router:`Navigation`,ngrx:`NgRx restore, dispatch and Store DevTools`,http:`HTTP mocking`,analog:`Analog API calls`,components:`Component edits`,tanstackQuery:`TanStack Query invalidate and refetch`,plugins:`Plugin actions`},Gw={refreshMs:e=>`poll every ${e} ms`,navigations:e=>`${e} navigations`,formTimeline:e=>`${e} form events`,httpCalls:e=>`${e} HTTP calls`,changeLog:e=>`${e} NgRx changes`,cdCycles:e=>`${e} change detection cycles`,cdSlowMs:e=>`slow cycles over ${e} ms`,capacitorCalls:e=>`${e} Capacitor plugin calls`,idleAfterMs:e=>e?`idle ${e/1e3} s after the last agent call`:`never idle`};function Kw(e){if(!e)return[];let t=[`On`];return e.minLength!==yw.minLength&&t.push(`${e.minLength} characters or more`),e.threshold!==yw.threshold&&t.push(`up to ${e.threshold} bits per character`),t}function qw(e){let t=[],n=(e,n)=>{n.length&&t.push({label:e,value:n.join(`, `)})},r=qC.filter(t=>e.inspectors[t]);n(`Inspectors off`,qC.filter(t=>!e.inspectors[t]).map(e=>Uw[e])),n(`Agent`,e.agent.readOnly?[`Read-only`]:[]),n(`Hidden from the agent`,r.filter(t=>!e.agent.tools[t]).map(e=>Uw[e])),n(`Blocked actions`,XC.filter(t=>e.inspectors[t]&&!e.actions[t]).map(e=>Ww[e])),n(`Extra secret names`,e.redaction.secretNames),n(`Unmasked names`,e.redaction.unmask),n(`Entropy masking`,Kw(e.redaction.entropy)),n(`Limits`,Object.keys(ZC).filter(t=>e.limits[t]!==ZC[t].default).map(t=>Gw[t](e.limits[t]))),e.popup.hotkey!==`Alt+Shift+P`&&n(`Popup shortcut`,[e.popup.hotkey===!1?`Off`:e.popup.hotkey]);let i=Object.entries(e.plugins??{});return n(`Plugins off`,i.filter(([,e])=>!e.enabled).map(([e])=>e)),n(`Plugin tools or actions off`,i.filter(([,e])=>e.enabled&&(!e.tools||!e.actions)).map(([e,t])=>`${e} (${[!t.tools&&`tools`,!t.actions&&`actions`].filter(Boolean).join(`, `)})`)),n(`Lint rules off`,Object.entries(e.lint?.rules??{}).filter(([,e])=>!e).map(([e])=>e)),t}function Jw(e){let t={tags:[],directives:[],text:``},n=e.toLowerCase();return n=n.replace(/<\s*([^<>\s]+)\s*>/g,(e,n)=>(t.tags.push({value:n,exact:!0}),` `)),n=n.replace(/\[\s*([^[\]\s]+)\s*\]/g,(e,n)=>(t.directives.push({value:n,exact:!0}),` `)),n=n.replace(/(^|\s)([<[])([^<>[\]\s]*)$/,(e,n,r,i)=>(i&&(r===`<`?t.tags:t.directives).push({value:i,exact:!1}),n)),t.text=n.replace(/\s+/g,` `).trim(),t}function Yw(e){return!e.tags.length&&!e.directives.length&&!e.text}function Xw(e,t){let n=e.tag.toLowerCase(),r=(e.directives??[]).map(e=>e.toLowerCase()),i=(e.attributes??[]).map(e=>e.toLowerCase()),a=[...r,...r.map(e=>e.replace(/directive$/,``)),...i];for(let{value:e,exact:r}of t.tags)if(r?n!==e:!n.startsWith(e))return!1;for(let{value:e,exact:n}of t.directives)if(!a.some(t=>n?t===e:t.includes(e)))return!1;if(t.text){let a=t.text;if(!(e.name.toLowerCase().includes(a)||n.includes(a)||r.some(e=>e.includes(a))||i.some(e=>e.includes(a))))return!1}return!0}var Zw={debugApi:{subject:`Live data`,since:`12.0`},injectorProviders:{subject:`Reading providers and environment injectors`,since:`16.2`},injectorMetadata:{subject:`The full injector tree`,since:`17.0`},isSignal:{subject:`Telling signals apart in component values`,since:`17.1`},signalGraph:{subject:`The live signal graph`,since:`20.1`},deferBlocks:{subject:`Listing @defer blocks`,since:`19.1`},forBlocks:{subject:`Listing @for blocks`,since:`22.0`},profiler:{subject:`Recording change detection`,since:`20.0`},angularProfiling:{subject:`Angular profiling`,since:`20.0`},routerInstance:{subject:`The router debug util`,since:`20.3`}};function Qw(e){let t=/^(\d+)\.(\d+)/.exec(e.trim());return t?`${t[1]}.${t[2]}`:e.trim()}function $w(e){let t=/^(\d+)(?:\.(\d+))?/.exec(e.trim());return t?[Number(t[1]),Number(t[2]??0)]:null}function eT(e,t){let n=$w(e),r=$w(t);return!n||!r?!1:n[0]===r[0]?n[1]>=r[1]:n[0]>r[0]}function tT(e,t){return e?.capabilities?.[t]===!1}function nT(e,t){let n=Zw[e],r=`${n.subject} needs Angular ${n.since} or later`;if(!t)return`${r}; this page's Angular version is unknown.`;let i=Qw(t);return eT(t,n.since)?`${r} in a development build; this page runs Angular ${i} without it, which usually means a production build.`:`${r}; this page runs Angular ${i}.`}function rT(e){return e.hydration===`hydrated`&&e.hydratedAt===void 0?`hydrated before inspection`:e.hydration}var iT=/^on (viewport|interaction|hover)\b/;function aT(e){let t=e.filter(e=>!/^(prefetch|hydrate) /.test(e));return t.length>0&&t.every(e=>iT.test(e))}function oT(e,t){let n=[],r=e.since>0?Math.max(0,Math.round((t-e.since)/1e3)):0;return e.state===`error`&&n.push({kind:`failed`,text:`failed to load`,label:`failed`}),(e.state===`placeholder`||e.state===`initial`)&&r*1e3>=1e4&&n.push(aT(e.triggers)?{kind:`waiting`,text:`waiting for its trigger for ${r}s`,label:`waiting ${r}s`,seconds:r}:{kind:`stuck`,text:`on its placeholder for ${r}s`,label:`stuck ${r}s`,seconds:r}),e.hydrateNever?n.push({kind:`hydrate-never`,text:`hydrate never: the server HTML stays static`,label:`hydrate never`}):e.hydration===`dehydrated`&&n.push({kind:`dehydrated`,text:`still dehydrated`,label:`dehydrated`}),n}var sT=`pangular-page-id`,cT=null;function lT(e){cT=e}function uT(){if(cT)return cT;try{return new URLSearchParams(location.search).get(`pageId`)||(window.top===window?null:sessionStorage.getItem(sT)||null)}catch{return null}}function dT(e,t){if(t.chosen&&e[t.chosen])return t.chosen;if(t.host)return t.host;if(t.previous&&e[t.previous])return t.previous;let n=null;for(let t of Object.values(e))(!n||t.reportedAt>n.reportedAt)&&(n=t);return n?.pageId??null}function fT(e,t){return e?t?e.pages?.[t]??null:e:null}function pT(e,t){return e?t?e.pages?.[t]??null:e.graph??null:null}function mT(){return{functions:[...Hw]}}function hT(e,t,n){return e?e.scope(`pangular`).rpc.call(t,...n===void 0?[]:[n]):Promise.resolve(null)}function gT(e,t,n){return hT(e,t,n).then(e=>e,()=>null)}function _T(e){return e?.connectionMeta.backend===`static`}function vT(e){let t=()=>{let t=e()?.scope(`pangular`).rpc;t&&(t.callEvent(`request-page-highlight`,null),t.callEvent(`request-form-highlight`,null))};return addEventListener(`pagehide`,t),()=>{removeEventListener(`pagehide`,t),t()}}function yT(e=globalThis){delete e.__DEVFRAME_CONNECTION__,delete e.__DEVFRAME_CONNECTION_META__}function bT(e){return e.split(`@`)[1]??``}var xT={signal:`Signal Forms`,reactive:`Reactive`,template:`Template-driven`},ST={own:`validator`,directive:`template attribute`,tree:`cross-field rule`,async:`async`,parse:`parse`,submission:`server`,schema:`schema`,manual:`setErrors`},CT=gT,wT={key:`name looks secret`,"input-type":`password input`,autocomplete:`autocomplete is a secret kind`,marker:`marked as mask`,parent:`inside a secret group`,config:`listed in mask`};function TT(e){return wT[e]??e}var ET=`https://pangular-inspector.dev/security/#opt-fields-in-or-out`;async function DT(e,t){return await CT(e,`request-form-action`,t)??{ok:!1,error:`The devtools server did not answer.`}}function OT(e){return`${e.ok?e.message??`Done.`:e.error??`Failed.`}${e.skipped?.length?` Skipped: ${e.skipped.map(e=>`${e.path} (${e.reason})`).join(`, `)}.`:``}${e.status?` Status: ${e.status}.`:``}`}var kT=/"(?:[^"\\\n]|\\.)*"|`([^`\n]*)`|\*\*/g;function AT(e){return(e??``).replace(/^_Labels, paths.*_\n\n/,``).replace(kT,(e,t)=>t??(e===`**`?``:e))}function jT(e,t){let n=Math.max(0,e-t),r=Math.ceil(n/6e4);if(r<60)return`in ${r} min`;let i=Math.round(r/60);return i<48?`in ${i} ${i===1?`hour`:`hours`}`:`in ${Math.round(i/24)} days`}function MT(e,t){return e.length?e.join(`, `):t}function NT(e,t){let n=[];if(n.push({label:`One-time code`,value:e.auth?e.oneTimeCode.state===`locked`?`Locked after too many wrong codes: run pangular token --rotate or restart the server for a new code`:e.oneTimeCode.expiresAt?`On, current code expires ${jT(e.oneTimeCode.expiresAt,t)}`:`On`:`Off: every client is trusted`}),e.auth){let{count:r,nextExpiresAt:i,lastExpiresAt:a}=e.tokens;n.push({label:`Trusted clients`,value:r?r===1||i===a?`${r} ${r===1?`token`:`tokens`}, expires ${jT(i??t,t)}`:`${r} tokens, the first expires ${jT(i??t,t)}, the last ${jT(a??t,t)}`:`None`}),e.pairingTokens&&n.push({label:`Pairing tokens`,value:`${e.pairingTokens} accepted instead of the code`})}return e.reported?(n.push({label:`Reach`,value:e.network?`This machine and other machines`:`This machine only`}),n.push({label:`Allowed origins`,value:e.originCheck?`${MT(e.allowedOrigins,`This machine and the Chrome extension`)}${e.customOrigins?`, plus a custom origin check`:``}`:`Any: the origin check is off`}),(e.allowedHosts===`any`||e.allowedHosts.length)&&n.push({label:`Allowed hosts`,value:e.allowedHosts===`any`?`Any`:e.allowedHosts.join(`, `)}),n):n}function PT(e){return Bw(e?.connectionMeta)}function FT(e){return Array.isArray(e)&&e.every(e=>typeof e==`string`)?[...e]:void 0}function IT(e,t){if(!t||typeof t!=`object`)return e;let n=FT(t.secretNames),r=FT(t.unmask);if(!n||!r)return e;let i=ww(t.entropy);return{...e,redaction:i?{secretNames:n,unmask:r,entropy:i}:{secretNames:n,unmask:r}}}function LT(e,t){return PT(e).actions[t]}var RT={components:`components`,routes:`router`,signals:`signals`,injectors:`injectors`,store:`ngrx`,queries:`tanstackQuery`,forms:`forms`,pipes:`pipes`,network:`http`,analog:`analog`,capacitor:`capacitor`,errors:`errors`,plugins:`plugins`,performance:`performance`,build:`build`};function zT(e){return RT[e]}var BT=`https://pangular-inspector.dev/getting-started/configuration/#inspectors-and-agent-tools`;function VT(e,t){let n=RT[e];return!n||t.inspectors[n]}function HT(e,t){e&1&&(w(),fg(0,`rect`,5)(1,`rect`,6)(2,`rect`,7)(3,`rect`,8))}function UT(e,t){e&1&&(w(),fg(0,`path`,9)(1,`circle`,10)(2,`circle`,11)(3,`path`,12))}function WT(e,t){e&1&&(w(),fg(0,`path`,13)(1,`path`,14))}function GT(e,t){e&1&&(w(),fg(0,`path`,15)(1,`path`,16)(2,`path`,17))}function KT(e,t){e&1&&(w(),fg(0,`circle`,18)(1,`path`,19)(2,`circle`,20))}function qT(e,t){e&1&&(w(),fg(0,`path`,1))}function JT(e,t){e&1&&(w(),fg(0,`path`,21)(1,`path`,22)(2,`path`,23)(3,`path`,24))}function YT(e,t){e&1&&(w(),fg(0,`ellipse`,25)(1,`path`,26)(2,`path`,27))}function XT(e,t){e&1&&(w(),fg(0,`rect`,28)(1,`path`,29)(2,`path`,30)(3,`path`,31))}function ZT(e,t){e&1&&(w(),fg(0,`circle`,32)(1,`path`,9)(2,`path`,33))}function QT(e,t){e&1&&(w(),fg(0,`path`,34)(1,`path`,35)(2,`path`,36)(3,`path`,37)(4,`circle`,38))}function $T(e,t){e&1&&(w(),fg(0,`circle`,39)(1,`path`,40))}function eE(e,t){e&1&&(w(),fg(0,`ellipse`,41)(1,`path`,42)(2,`path`,43)(3,`path`,44)(4,`path`,45))}function tE(e,t){e&1&&(w(),fg(0,`circle`,32)(1,`path`,46)(2,`path`,47))}function nE(e,t){e&1&&(w(),fg(0,`path`,48)(1,`path`,49))}function rE(e,t){e&1&&(w(),fg(0,`path`,50)(1,`path`,51)(2,`path`,52))}function iE(e,t){e&1&&(w(),fg(0,`path`,2))}function aE(e,t){e&1&&(w(),fg(0,`path`,3))}function oE(e,t){e&1&&(w(),fg(0,`path`,4))}var sE=class e{name=$.required();static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-tab-icon`]],inputs:{name:[1,`name`]},decls:20,vars:1,consts:[[`viewBox`,`0 0 24 24`,`width`,`16`,`height`,`16`,`fill`,`none`,`stroke`,`currentColor`,`stroke-width`,`1.8`,`stroke-linecap`,`round`,`stroke-linejoin`,`round`,`aria-hidden`,`true`],[`d`,`M22 12h-4l-3 9L9 3l-3 9H2`],[`d`,`M10 3h4v3a2 2 0 1 0 4 0V5h1a2 2 0 0 1 2 2v4h-2a2 2 0 1 0 0 4h2v4a2 2 0 0 1-2 2h-4v-2a2 2 0 1 0-4 0v2H7a2 2 0 0 1-2-2v-4H4a2 2 0 1 1 0-4h1V7a2 2 0 0 1 2-2h3Z`],[`d`,`m8 3 4 8 5-5 5 15H2L8 3z`],[`fill`,`currentColor`,`stroke`,`none`,`transform`,`translate(1.8 1.8) scale(0.85)`,`d`,`M24 3.7l-5.766 5.766 5.725 5.736-3.713 3.712L5.073 3.742 8.786.03l5.736 5.726L20.284 0 24 3.7zM.029 8.785l3.713-3.713 15.173 15.173-3.713 3.714-5.732-5.726L3.7 24 0 20.285l5.754-5.764L.029 8.785z`],[`x`,`3`,`y`,`3`,`width`,`7`,`height`,`9`,`rx`,`1.5`],[`x`,`14`,`y`,`3`,`width`,`7`,`height`,`5`,`rx`,`1.5`],[`x`,`14`,`y`,`12`,`width`,`7`,`height`,`9`,`rx`,`1.5`],[`x`,`3`,`y`,`16`,`width`,`7`,`height`,`5`,`rx`,`1.5`],[`d`,`M3 12h18`],[`cx`,`7`,`cy`,`12`,`r`,`2`],[`cx`,`14`,`cy`,`12`,`r`,`2`],[`d`,`M18 7v10`],[`d`,`M9 11l2 2 4-4`],[`d`,`M12 3 4 6v6c0 4.5 3.4 8.3 8 9 4.6-.7 8-4.5 8-9V6l-8-3Z`],[`d`,`M12 2 3 7l9 5 9-5-9-5Z`],[`d`,`m3 12 9 5 9-5`],[`d`,`m3 17 9 5 9-5`],[`cx`,`6`,`cy`,`19`,`r`,`3`],[`d`,`M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15`],[`cx`,`18`,`cy`,`5`,`r`,`3`],[`d`,`M12 22v-5`],[`d`,`M9 8V2`],[`d`,`M15 8V2`],[`d`,`M18 8v5a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V8Z`],[`cx`,`12`,`cy`,`5`,`rx`,`9`,`ry`,`3`],[`d`,`M3 5v14a9 3 0 0 0 18 0V5`],[`d`,`M3 12a9 3 0 0 0 18 0`],[`x`,`3`,`y`,`4`,`width`,`18`,`height`,`16`,`rx`,`2`],[`d`,`M7 9h10`],[`d`,`M7 13h6`],[`d`,`M7 17h8`],[`cx`,`12`,`cy`,`12`,`r`,`9`],[`d`,`M12 3a14 14 0 0 1 0 18a14 14 0 0 1 0-18Z`],[`d`,`M4 7h10`],[`d`,`M4 17h6`],[`d`,`m14 13 4 4-4 4`],[`d`,`M10 17h8`],[`cx`,`18`,`cy`,`7`,`r`,`3`],[`cx`,`12`,`cy`,`12`,`r`,`3`],[`d`,`M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z`],[`cx`,`11`,`cy`,`5`,`rx`,`8`,`ry`,`3`],[`d`,`M3 5v6c0 1.66 3.58 3 8 3`],[`d`,`M3 11v6c0 1.66 3.58 3 8 3`],[`d`,`M21 14a4 4 0 1 1-1.2-2.85L21 12.3`],[`d`,`M21 9v3.3h-3.3`],[`d`,`M12 7v6`],[`d`,`M12 16.5v.01`],[`d`,`M12 14l4-4`],[`d`,`M3.34 19a10 10 0 1 1 17.32 0`],[`d`,`M21 16V8l-9-5-9 5v8l9 5 9-5Z`],[`d`,`m3.3 7 8.7 5 8.7-5`],[`d`,`M12 22V12`]],template:function(e,t){if(e&1&&(w(),R(0,`svg`,0),A(1,HT,4,0)(2,UT,4,0)(3,WT,2,0)(4,GT,3,0)(5,KT,3,0)(6,qT,1,0,`:svg:path`,1)(7,JT,4,0)(8,YT,3,0)(9,XT,4,0)(10,ZT,3,0)(11,QT,5,0)(12,$T,2,0)(13,eE,5,0)(14,tE,3,0)(15,nE,2,0)(16,rE,3,0)(17,iE,1,0,`:svg:path`,2)(18,aE,1,0,`:svg:path`,3)(19,oE,1,0,`:svg:path`,4),z()),e&2){let e;D(),j((e=t.name())===`dashboard`?1:e===`timeline`?2:e===`audit`?3:e===`components`?4:e===`routes`?5:e===`signals`?6:e===`injectors`?7:e===`store`?8:e===`forms`?9:e===`network`?10:e===`pipes`?11:e===`settings`?12:e===`queries`?13:e===`errors`?14:e===`performance`?15:e===`build`?16:e===`plugins`?17:e===`analog`?18:e===`capacitor`?19:-1)}},styles:[`[_nghost-%COMP%] { + display: inline-flex; + flex-shrink: 0; +}`]})},cE=(e,t)=>t.tab,lE=(e,t)=>t.label;function uE(e,t){e&1&&K(0),e&2&&J(` `,W().meta()?.projectName,` `)}function dE(e,t){e&1&&K(0,` Project details unavailable `)}function fE(e,t){e&1&&K(0,` Loading… `)}function pE(e,t){e&1&&(K(0,` Run `),F(1,`code`),K(2,`pangular build`),I(),K(3,` again to rebuild the report. `))}function mE(e,t){e&1&&K(0,` Check that the dev server is running. `)}function hE(e,t){if(e&1){let e=B();F(0,`button`,13),H(`click`,function(){return S(e),C(W(2).loadMeta())}),K(1,`Try again`),I()}}function gE(e,t){if(e&1&&(F(0,`p`,11),A(1,pE,4,0)(2,mE,1,0),I(),A(3,hE,2,0,`button`,12)),e&2){let e=W();D(),j(e.staticReport()?1:2),D(2),j(e.staticReport()?-1:3)}}function _E(e,t){e&1&&(F(0,`li`,5)(1,`span`),K(2,`Analog`),I(),K(3),I()),e&2&&(D(3),q(t))}function vE(e,t){e&1&&(F(0,`li`)(1,`span`),K(2,`Change detection`),I(),K(3),I()),e&2&&(D(3),q(t))}function yE(e,t){if(e&1&&(F(0,`span`,20),K(1),I(),F(2,`span`,21),K(3),I()),e&2){let e=W().$implicit,t=W();D(),q(t.cards()[e.tab]?.value),D(2),q(t.cards()[e.tab]?.sub)}}function bE(e,t){e&1&&(F(0,`span`,22),K(1,`–`),I(),F(2,`span`,21),K(3,`Count unavailable`),I())}function xE(e,t){e&1&&(L(0,`span`,23),F(1,`span`,21),K(2,`Counting…`),I())}function SE(e,t){if(e&1){let e=B();F(0,`button`,14),H(`click`,function(){let t=S(e).$implicit;return C(W().navigate.emit(t.tab))}),F(1,`span`,15)(2,`span`,16),L(3,`app-tab-icon`,17),I(),F(4,`span`,18),K(5,`→`),I()(),F(6,`span`,19),K(7),I(),A(8,yE,4,2)(9,bE,4,0)(10,xE,3,0),I()}if(e&2){let e,n=t.$implicit,r=t.$index,i=W().stateOf(n.tab);n_(`animation-delay`,60*r,`ms`),k(`aria-busy`,i===`loading`),D(3),P(`name`,n.tab),D(4),q(n.label),D(),j((e=i)===`ready`?8:e===`error`?9:10)}}function CE(e,t){e&1&&(F(0,`p`),K(1,`Loading…`),I())}function wE(e,t){if(e&1&&(F(0,`div`)(1,`dt`),K(2),I(),F(3,`dd`),K(4),I()()),e&2){let e=t.$implicit;D(2),q(e.label),D(2),q(e.value)}}function TE(e,t){if(e&1&&(F(0,`dl`),M(1,wE,5,2,`div`,null,lE),I()),e&2){let e=W();D(),N(e.configItems())}}function EE(e,t){e&1&&(F(0,`p`),K(1,`Defaults`),I())}function DE(e,t){if(e&1&&(F(0,`div`)(1,`dt`),K(2),I(),F(3,`dd`),K(4),I()()),e&2){let e=t.$implicit;D(2),q(e.label),D(2),q(e.value)}}function OE(e,t){e&1&&(F(0,`section`,10)(1,`h2`,24),K(2,`Security`),I(),F(3,`dl`),M(4,DE,5,2,`div`,null,lE),I()()),e&2&&(D(4),N(t))}var kE=[{tab:`components`,label:`Components`},{tab:`routes`,label:`Routes`},{tab:`signals`,label:`Signals`},{tab:`injectors`,label:`Injectors`},{tab:`store`,label:`NgRx declarations`},{tab:`pipes`,label:`Pipes`},{tab:`forms`,label:`Forms`},{tab:`network`,label:`SSR & HTTP`}];function AE(e){return kE.filter(t=>VT(t.tab,e))}var jE={zoneless:`Zoneless`,zone:`zone.js`,"zone-unused":`Zoneless, zone.js loaded`};function ME(e){return e&&Object.hasOwn(jE,e)?jE[e]:null}var NE=new Set([`signal`,`computed`,`linkedSignal`,`effect`]),PE={action:[`action`,`actions`],reducer:[`reducer`,`reducers`],effect:[`effect`,`effects`],selector:[`selector`,`selectors`],feature:[`feature`,`features`],"store-setup":[`setup call`,`setup calls`],"signal-store":[`signal store`,`signal stores`],"signal-state":[`signal state`,`signal states`],"signal-method":[`signal method`,`signal methods`]};function FE(e,t,n){return`${e} ${e===1?t:n}`}function IE(e,t){for(let n of e??[])t(n),IE(n.children,t)}function LE(e){if(!e.some(e=>e.kind))return{value:e.length,sub:`discovered in source`};let t=e.filter(e=>e.kind===`directive`).length;return{value:e.length-t,sub:`components · ${FE(t,`directive`,`directives`)}`}}function RE(e){if(!e.some(e=>e.kind))return{value:e.length,sub:`route entries in source`};let t=e.filter(e=>e.kind===`redirect`).length;return{value:new Set(e.filter(e=>e.kind===`page`).map(e=>e.fullPath??e.path??``)).size,sub:`navigable paths · ${FE(t,`redirect`,`redirects`)}`}}function zE(e){let t=new Map;for(let n of e)n.kind&&t.set(n.kind,(t.get(n.kind)??0)+1);if(!t.size)return{value:e.length,sub:`declarations in source`};let n=[...t].sort((e,t)=>t[1]-e[1]).map(([e,t])=>{let[n,r]=PE[e]??[e,e];return FE(t,n,r)});return{value:e.length,sub:n.join(` · `)}}function BE(e,t){let n=e?.forms??[],r=t?n.filter(e=>bT(e.id)===t):n;if(!r.length)return{value:0,sub:`no forms on the page yet`};let i=r.filter(e=>e.root?.status===`INVALID`).length;return{value:r.length,sub:`${r.length===1?`form`:`forms`} · ${i} invalid`}}function VE(e){return e.cancelled||e.status===void 0?!1:e.status===0||e.status>=400}function HE(e,t){let n=e?.pages??[],r=t?n.find(e=>e.pageId===t)??null:n.reduce((e,t)=>!e||t.reportedAt>e.reportedAt?t:e,null),i=t&&!r?[]:e?.serverCalls??[],a=r?.initialUrl,o=[...a?i.filter(e=>!e.pageUrl||e.pageUrl===a):i,...r?.calls??[]];if(!o.length)return{value:0,sub:`no calls recorded yet`};let s=o.filter(VE).length;return{value:o.length,sub:`${o.length===1?`call`:`calls`} · ${s} failed`}}var UE=class e{rpc=$(null);navigate=lv();staticReport=X(()=>_T(this.rpc()));meta=T(null);security=T(null);redaction=T(null);config=X(()=>IT(PT(this.rpc()),this.redaction()));stats=X(()=>this.rpc()?AE(this.config()):[]);configItems=X(()=>qw(this.config()));metaState=T(`loading`);states=T({});rows=T({});injectorTree=T(null);signalGraph=T(null);formsState=T(null);httpState=T(null);pageId=uT();destroyRef=x(Ss);stopLive=[];zone=X(()=>ME(fT(this.injectorTree(),this.pageId)?.zone));liveInjectors=X(()=>{let e=fT(this.injectorTree(),this.pageId);if(!e?.roots?.length)return null;let t=0,n=0,r=e=>{t++,n+=e.providers?.length??0};return IE(e.roots,r),IE(e.environment,r),{injectors:t,providers:n}});liveSignals=X(()=>{let e=pT(this.signalGraph(),this.pageId);return e?.nodes?.length?e.nodes.filter(e=>NE.has(e.kind??``)).length:null});cards=X(()=>{let e=this.rows(),t={};if(e.components&&(t.components=LE(e.components)),e.routes&&(t.routes=RE(e.routes)),e.store&&(t.store=zE(e.store)),e.pipes){let n=e.pipes.filter(e=>e.builtin).length;t.pipes={value:e.pipes.length-n,sub:n?`custom pipes · ${n} built-in in use`:`custom pipes in source`}}let n=this.liveSignals();n===null?e.signals&&(t.signals={value:e.signals.length,sub:`signal declarations in source`}):t.signals={value:n,sub:e.signals?`live on the page · ${e.signals.length} declared in source`:`live on the page`};let r=this.liveInjectors();r?t.injectors={value:r.injectors,sub:`live injectors · ${FE(r.providers,`provider`,`providers`)}`}:e.injectors&&(t.injectors={value:e.injectors.filter(e=>e.type!==`injection`).length,sub:`provider declarations in source`});let i=this.formsState();i&&(t.forms=BE(i,this.pageId));let a=this.httpState();return a&&(t.network=HE(a,this.pageId)),t});loadMeta(e=this.rpc()){e&&(this.metaState.set(`loading`),e.scope(`pangular`).rpc.call(`build-meta`).then(t=>{this.rpc()===e&&(this.meta.set(t),this.metaState.set(`ready`))}).catch(()=>{this.rpc()===e&&this.metaState.set(`error`)}))}loadRedaction(e){this.redaction.set(null),!_T(e)&&hT(e,`redaction-config`).then(t=>{this.rpc()===e&&this.redaction.set(t)}).catch(()=>{})}loadSecurity(e){this.security.set(null),!_T(e)&&gT(e,`security-status`).then(t=>{this.rpc()===e&&t&&this.security.set(NT(t,Date.now()))})}constructor(){E(()=>{let e=this.rpc();if(!e)return;let t=e.scope(`pangular`);this.states.set({}),this.rows.set({}),this.formsState.set(null),this.httpState.set(null),this.loadMeta(e),this.loadRedaction(e),this.loadSecurity(e);let n=PT(e),r=e=>VT(e,n);r(`components`)&&this.load(t.rpc.call(`get-components`),`components`),r(`routes`)&&this.load(t.rpc.call(`get-routes`),`routes`),r(`signals`)&&this.load(t.rpc.call(`get-signals`),`signals`),r(`injectors`)&&this.load(t.rpc.call(`get-providers`),`injectors`),r(`store`)&&this.load(t.rpc.call(`get-ngrx-store`),`store`),r(`pipes`)&&this.load(t.rpc.call(`get-pipes`),`pipes`),this.watchLive(e,n)}),this.destroyRef.onDestroy(()=>this.unwatch())}stateOf(e){return(e===`signals`?this.liveSignals():e===`injectors`?this.liveInjectors():null)===null?this.states()[e]??`loading`:`ready`}load(e,t){let n=e=>this.states.update(n=>({...n,[t]:e}));e.then(e=>{this.rows.update(n=>({...n,[t]:Array.isArray(e)?e:[]})),n(`ready`)}).catch(()=>n(`error`))}async watchLive(e,t){this.unwatch();let n=e.scope(`pangular`).rpc,r=async(t,r,i)=>{let a=e=>i&&this.states.update(t=>({...t,[i]:e}));try{let i=await n.sharedState(t);if(this.destroyRef.destroyed||this.rpc()!==e)return;r(i.value()),a(`ready`),this.stopLive.push(i.on(`updated`,e=>r(e)))}catch{if(this.destroyRef.destroyed||this.rpc()!==e)return;r(null),a(`error`)}};await Promise.all([t.inspectors.injectors&&r(`injector-tree`,e=>this.injectorTree.set(e)),t.inspectors.signals&&r(`signal-graph`,e=>this.signalGraph.set(e)),VT(`forms`,t)&&r(`forms`,e=>this.formsState.set(e??{}),`forms`),VT(`network`,t)&&r(`http`,e=>this.httpState.set(e??{}),`network`)])}unwatch(){for(let e of this.stopLive.splice(0))e()}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-dashboard`]],inputs:{rpc:[1,`rpc`]},outputs:{navigate:`navigate`},decls:34,vars:15,consts:[[`aria-labelledby`,`project-title`,1,`project`],[1,`project-head`],[1,`eyebrow`],[`id`,`project-title`],[1,`chips`],[1,`analog`],[1,`grid`],[`type`,`button`,1,`stat`,3,`animation-delay`],[`aria-labelledby`,`config-title`,1,`config`],[`id`,`config-title`],[`aria-labelledby`,`security-title`,1,`config`],[1,`hint`],[`type`,`button`,1,`retry`],[`type`,`button`,1,`retry`,3,`click`],[`type`,`button`,1,`stat`,3,`click`],[1,`top`],[1,`icon`],[3,`name`],[`aria-hidden`,`true`,1,`go`],[1,`label`],[1,`big`],[1,`sub`],[1,`big`,`muted`],[`aria-hidden`,`true`,1,`big`,`skeleton`],[`id`,`security-title`]],template:function(e,t){if(e&1&&(F(0,`section`,0)(1,`div`,1)(2,`p`,2),K(3,`Project`),I(),F(4,`h2`,3),A(5,uE,1,1)(6,dE,1,0)(7,fE,1,0),I(),A(8,gE,4,2),I(),F(9,`ul`,4)(10,`li`)(11,`span`),K(12,`Angular`),I(),K(13),I(),F(14,`li`)(15,`span`),K(16,`TypeScript`),I(),K(17),I(),F(18,`li`)(19,`span`),K(20,`SSR`),I(),K(21),I(),A(22,_E,4,1,`li`,5),A(23,vE,4,1,`li`),I()(),F(24,`div`,6),M(25,SE,11,6,`button`,7,cE),I(),F(27,`section`,8)(28,`h2`,9),K(29,`Configuration`),I(),A(30,CE,2,0,`p`)(31,TE,3,0,`dl`)(32,EE,2,0,`p`),I(),A(33,OE,6,0,`section`,10)),e&2){let e,n,r,i;k(`aria-busy`,t.metaState()===`loading`),D(4),G(`muted`,t.metaState()!==`ready`),D(),j((e=t.metaState())===`ready`?5:e===`error`?6:7),D(3),j(t.metaState()===`error`?8:-1),D(),G(`pending`,t.metaState()===`loading`),D(4),q(t.meta()?.angularVersion??`…`),D(4),q(t.meta()?.typescript??`…`),D(4),q(t.meta()?t.meta()?.ssr?`On`:`Off`:`…`),D(),j((n=t.meta()?.analog)?22:-1,n),D(),j((r=t.zone())?23:-1,r),D(2),N(t.stats()),D(2),k(`aria-busy`,!t.rpc()),D(3),j(t.rpc()?t.configItems().length?31:32:30),D(3),j((i=t.security())?33:-1,i)}},dependencies:[sE],styles:[`[_nghost-%COMP%] { + display: block; + max-width: 1200px; +} + +.project[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + justify-content: space-between; + gap: 16px 24px; + margin-bottom: 16px; + padding: 24px; + border: 1px solid var(--%NS%border); + border-radius: var(--%NS%radius); + background: radial-gradient(420px 180px at 100% 0%, var(--%NS%accent-soft), transparent 70%), linear-gradient(180deg, var(--%NS%surface-2), var(--%NS%surface)); + box-shadow: var(--%NS%shadow); +} + +.project-head[_ngcontent-%COMP%] { + flex: 1 1 240px; + min-width: 0; +} + +.eyebrow[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + margin: 0 0 4px; + color: var(--%NS%accent); +} + +h2[_ngcontent-%COMP%] { + margin: 0; + color: var(--%NS%text-strong); + font-size: 24px; + line-height: 1.25; + letter-spacing: -0.02em; + overflow-wrap: anywhere; +} + +h2.muted[_ngcontent-%COMP%] { + color: var(--%NS%text-2); + font-weight: 600; +} + +.hint[_ngcontent-%COMP%] { + margin: 8px 0 0; + color: var(--%NS%text-2); + font-size: 13px; + line-height: 1.5; +} + +.retry[_ngcontent-%COMP%] { + display: inline-flex; + margin-top: 8px; + align-items: center; + height: var(--%NS%control-h); + padding: 0 14px; + border: 1px solid var(--%NS%control-border); + border-radius: var(--%NS%radius-sm); + background: var(--%NS%surface-2); + color: var(--%NS%text); + font: inherit; + font-weight: 500; + cursor: pointer; +} +.retry[_ngcontent-%COMP%]:hover { + background: var(--%NS%surface-3); + border-color: var(--%NS%accent-line); +} +.retry[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.chips[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + gap: 8px; + min-width: 0; + margin: 0; + padding: 0; + list-style: none; +} + +.chips[_ngcontent-%COMP%] li[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; + gap: 8px; + height: 28px; + max-width: 100%; + padding: 0 12px; + border: 1px solid var(--%NS%border-strong); + border-radius: 99px; + background: var(--%NS%bg); + color: var(--%NS%text-strong); + font-size: 12px; + font-weight: 600; + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.chips[_ngcontent-%COMP%] span[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-weight: 500; +} + +.chips.pending[_ngcontent-%COMP%] li[_ngcontent-%COMP%] { + color: var(--%NS%text-3); +} + +.chips[_ngcontent-%COMP%] .analog[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, #dd0330 55%, transparent); +} + +.grid[_ngcontent-%COMP%] { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(min(100%, 150px), 1fr)); + gap: 12px; +} + +.stat[_ngcontent-%COMP%] { + display: flex; + flex-direction: column; + align-items: flex-start; + min-width: 0; + padding: 16px; + border: 1px solid var(--%NS%border); + border-radius: var(--%NS%radius); + background: var(--%NS%surface); + color: inherit; + text-align: left; + font: inherit; + cursor: pointer; + animation: enter 0.4s var(--%NS%ease) both; + transition: border-color 0.2s var(--%NS%ease), transform 0.2s var(--%NS%ease), background-color 0.2s var(--%NS%ease), box-shadow 0.2s var(--%NS%ease); +} + +.stat[_ngcontent-%COMP%]:hover { + border-color: var(--%NS%accent-line); + background: var(--%NS%surface-2); + box-shadow: var(--%NS%shadow); + transform: translateY(-2px); +} + +.stat[_ngcontent-%COMP%]:active { + transform: translateY(0); + background: var(--%NS%surface-3); +} + +.stat[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.top[_ngcontent-%COMP%] { + display: flex; + align-items: center; + justify-content: space-between; + align-self: stretch; + margin-bottom: 12px; +} + +.icon[_ngcontent-%COMP%] { + display: grid; + place-items: center; + width: 32px; + height: 32px; + border: 1px solid var(--%NS%accent-line); + border-radius: var(--%NS%radius-sm); + background: var(--%NS%accent-soft); + color: var(--%NS%accent); +} + +.go[_ngcontent-%COMP%] { + display: grid; + place-items: center; + width: 24px; + height: 24px; + color: var(--%NS%text-3); + transition: transform 0.2s var(--%NS%ease), color 0.2s var(--%NS%ease); +} + +.stat[_ngcontent-%COMP%]:hover .go[_ngcontent-%COMP%], +.stat[_ngcontent-%COMP%]:focus-visible .go[_ngcontent-%COMP%] { + color: var(--%NS%accent); + transform: translateX(3px); +} + +.label[_ngcontent-%COMP%] { + max-width: 100%; + overflow: hidden; + color: var(--%NS%text-2); + font-size: 13px; + font-weight: 500; + line-height: 16px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.big[_ngcontent-%COMP%] { + margin: 4px 0; + color: var(--%NS%text-strong); + font-size: 32px; + font-weight: 750; + line-height: 40px; + letter-spacing: -0.03em; + font-variant-numeric: tabular-nums; +} + +.big.muted[_ngcontent-%COMP%] { + color: var(--%NS%text-3); +} + +.skeleton[_ngcontent-%COMP%] { + display: block; + width: 56px; + height: 32px; + margin-block: 8px; + border-radius: var(--%NS%radius-sm); + background: linear-gradient(90deg, var(--%NS%surface-2), var(--%NS%surface-3), var(--%NS%surface-2)) 0 0/200% 100%; + animation: _ngcontent-%COMP%_shimmer 1.4s linear infinite; +} + +.sub[_ngcontent-%COMP%] { + max-width: 100%; + color: var(--%NS%text-3); + font-size: 12px; + line-height: 16px; +} + +.config[_ngcontent-%COMP%] { + margin-top: 16px; + padding: 16px; + border: 1px solid var(--%NS%border); + border-radius: var(--%NS%radius); + background: var(--%NS%surface); +} + +.config[_ngcontent-%COMP%] h2[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + margin: 0 0 8px; + color: var(--%NS%text-2); +} + +.config[_ngcontent-%COMP%] p[_ngcontent-%COMP%], +.config[_ngcontent-%COMP%] dl[_ngcontent-%COMP%] { + margin: 0; + color: var(--%NS%text); + font-size: 13px; + line-height: 1.5; +} + +.config[_ngcontent-%COMP%] dl[_ngcontent-%COMP%] { + display: grid; + gap: 4px; +} + +.config[_ngcontent-%COMP%] dl[_ngcontent-%COMP%] div[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + gap: 4px 8px; +} + +.config[_ngcontent-%COMP%] dt[_ngcontent-%COMP%] { + color: var(--%NS%text-2); +} + +.config[_ngcontent-%COMP%] dt[_ngcontent-%COMP%]::after { + content: ":"; +} + +.config[_ngcontent-%COMP%] dd[_ngcontent-%COMP%] { + margin: 0; + color: var(--%NS%text-strong); + overflow-wrap: anywhere; +} + +@keyframes _ngcontent-%COMP%_shimmer { + to { + background-position: -200% 0; + } +} +@media (max-width: 480px) { + .project[_ngcontent-%COMP%] { + padding: 16px; + } + h2[_ngcontent-%COMP%] { + font-size: 20px; + } + .stat[_ngcontent-%COMP%] { + padding: 12px; + } + .big[_ngcontent-%COMP%] { + font-size: 28px; + line-height: 36px; + } +} +@media (prefers-reduced-motion: reduce) { + .stat[_ngcontent-%COMP%], + .stat[_ngcontent-%COMP%]:hover, + .stat[_ngcontent-%COMP%]:active { + transform: none; + } + .stat[_ngcontent-%COMP%]:hover .go[_ngcontent-%COMP%] { + transform: none; + } + .skeleton[_ngcontent-%COMP%] { + animation: none; + } +}`]})},WE=`pangular:panel-action-result`,GE=6e3,KE=0;function qE(e=window){return e.location.protocol===`chrome-extension:`&&e.parent!==e}function JE(e,t=window){let n=`pa-${Date.now().toString(36)}-${++KE}`;return new Promise(r=>{let i=e=>{t.clearTimeout(o),t.removeEventListener(`message`,a),r(e)},a=({source:e,origin:r,data:a})=>{if(e!==t.parent||r!==t.location.origin)return;let o=a;if(o?.type!==WE||o.requestId!==n)return;let{opened:s,error:c}=a;i({ok:o.ok===!0,...s===`file`||s===`class`?{opened:s}:{},...typeof c==`string`?{error:c}:{}})},o=t.setTimeout(()=>i({ok:!1,error:`timeout`}),GE);t.addEventListener(`message`,a),t.parent.postMessage({...e,requestId:n},t.location.origin)})}var YE=class e{available=qE();reveal(e,t){return JE({type:`pangular:reveal-element`,pageId:e,id:t})}openSource(e,t,n){return JE({type:`pangular:open-source`,pageId:e,id:t,...n?{file:n.file,line:n.line}:{}})}static ɵfac=function(t){return new(t||e)};static ɵprov=kl({token:e,factory:e.ɵfac})};function XE(e){return new Date(e).toLocaleTimeString()}var ZE=/[ \t\n\r]/;function QE(e){let t=e.trim(),n;try{n=JSON.parse(t)}catch{return null}if(typeof n!=`object`||!n)return null;let r=``,i=0,a=()=>`\n${` `.repeat(i)}`;for(let e=0;et.name;function tD(e,t){e&1&&(R(0,`span`,11),K(1,`impure`),z())}function nD(e,t){e&1&&(R(0,`span`,11),K(1,`churning`),z())}function rD(e,t){e&1&&(R(0,`span`,11),K(1,`stale?`),z())}function iD(e,t){if(e&1&&(R(0,`span`,10),K(1),z()),e&2){let e=W(2).$implicit,t=W(3);D(),q(t.plural(e.calls,`call`))}}function aD(e,t){if(e&1){let e=B();R(0,`button`,7),U(`click`,function(){S(e);let t=W().$implicit;return C(W(3).showPipe.emit(t.name))}),R(1,`span`,8),K(2,`Show in Pipes:`),z(),R(3,`span`,9),K(4),z(),R(5,`span`,10),K(6),z(),A(7,tD,2,0,`span`,11),A(8,nD,2,0,`span`,11),A(9,rD,2,0,`span`,11),A(10,iD,2,1,`span`,10),z()}if(e&2){let e=W().$implicit;D(4),q(e.name),D(2),J(``,e.uses,`×`),D(),j(e.isPure?-1:7),D(),j(e.churn?8:-1),D(),j(e.stale?9:-1),D(),j(e.calls===void 0?-1:10)}}function oD(e,t){e&1&&(R(0,`span`,11),K(1,`impure`),z())}function sD(e,t){if(e&1&&(R(0,`span`,6)(1,`span`,9),K(2),z(),R(3,`span`,10),K(4),z(),A(5,oD,2,0,`span`,11),z()),e&2){let e=W().$implicit;D(2),q(e.name),D(2),J(``,e.uses,`×`),D(),j(e.isPure?-1:5)}}function cD(e,t){if(e&1&&(R(0,`li`),A(1,aD,11,6,`button`,5)(2,sD,6,3,`span`,6),z()),e&2){let e=W(3);D(),j(e.canShowPipes()?1:2)}}function lD(e,t){if(e&1&&(R(0,`ul`,3),M(1,cD,3,1,`li`,null,eD),z(),R(3,`p`,4),K(4,` Call counts cover every component that uses the pipe. Record calls in the Pipes tab to see them. `),z()),e&2){let e=W(2);D(),N(e.rows())}}function uD(e,t){e&1&&(R(0,`p`,2),K(1,`No pipe is in use in the rendered part of its template.`),z())}function dD(e,t){if(e&1&&(R(0,`div`,0)(1,`h3`),K(2,` Pipes in template `),R(3,`span`,1),K(4),z()(),A(5,lD,5,0)(6,uD,2,0,`p`,2),z()),e&2){let e=W();D(4),q(e.rows().length),D(),j(e.rows().length?5:6)}}function fD(e,t,n){let r=(t?e?.pages?.find(e=>e.pageId===t):void 0)?.pipes??e?.pipes??[],i=[];for(let e of r){let t=e.components.find(e=>e.name===n);t&&i.push({name:e.name,isPure:e.isPure,uses:t.count,...e.call?{calls:e.call.callCount}:{},churn:!!e.churn,stale:!!e.stale})}return i.sort((e,t)=>e.name.localeCompare(t.name))}var pD=class e{plural=$E;destroyRef=x(Ss);rpc=$(null);pageId=$(void 0);component=$.required();canShowPipes=$(!1);showPipe=lv();snapshot=T(void 0);loaded=T(!1);rows=X(()=>fD(this.snapshot(),this.pageId(),this.component()));unsubscribe;constructor(){E(()=>{let e=this.rpc();e&&this.subscribe(e)}),this.destroyRef.onDestroy(()=>this.unsubscribe?.())}async subscribe(e){try{let t=await e.scope(`pangular`).rpc.sharedState(`pipe-usage`);if(this.destroyRef.destroyed)return;let n=e=>{this.snapshot.set(e),this.loaded.set(!0)};n(t.value()),this.unsubscribe?.(),this.unsubscribe=t.on(`updated`,n)}catch{}}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-component-pipes`]],inputs:{rpc:[1,`rpc`],pageId:[1,`pageId`],component:[1,`component`],canShowPipes:[1,`canShowPipes`]},outputs:{showPipe:`showPipe`},decls:1,vars:1,consts:[[1,`block`],[1,`pill`],[1,`empty-line`],[1,`chips`],[1,`hint`],[`type`,`button`,1,`pipe`],[1,`pipe`],[`type`,`button`,1,`pipe`,3,`click`],[1,`sr-only`],[1,`mono`],[1,`note`],[1,`flag`,`warn`]],template:function(e,t){e&1&&A(0,dD,7,2,`div`,0),e&2&&j(t.loaded()?0:-1)},styles:[`[_nghost-%COMP%] { + display: block; +} + +.block[_ngcontent-%COMP%] { + padding: 14px 16px; +} + +h3[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + display: flex; + align-items: center; + gap: 6px; + margin: 0 0 8px; +} + +.pill[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 16px; + padding: 0 5px; + border-radius: 99px; + background: var(--%NS%surface-3); + color: var(--%NS%text-2); + font-size: 10px; + font-weight: 600; + letter-spacing: 0; +} + +.chips[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin: 0; + padding: 0; + list-style: none; +} + +.pipe[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; + gap: 6px; + min-height: var(--%NS%control-h); + padding: 0 10px; + border: 1px solid var(--%NS%control-border); + border-radius: var(--%NS%radius-sm); + background: var(--%NS%surface-2); + color: var(--%NS%text); + font: inherit; + font-size: 12px; +} + +button.pipe[_ngcontent-%COMP%] { + cursor: pointer; + transition: background-color 150ms var(--%NS%ease), border-color 150ms var(--%NS%ease); +} + +button.pipe[_ngcontent-%COMP%]:hover { + border-color: var(--%NS%accent-line); + background: var(--%NS%surface-3); +} + +button.pipe[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.mono[_ngcontent-%COMP%] { + font-family: var(--%NS%font-mono); + color: var(--%NS%text-strong); +} + +.note[_ngcontent-%COMP%] { + color: var(--%NS%text-2); + font-variant-numeric: tabular-nums; +} + +.flag[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.flag.warn[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%warn) 30%, transparent); + background: color-mix(in srgb, var(--%NS%warn) 12%, transparent); + color: var(--%NS%warn-text); + padding: 0 6px; + border-radius: 99px; +} + +.hint[_ngcontent-%COMP%], +.empty-line[_ngcontent-%COMP%] { + margin: 8px 0 0; + color: var(--%NS%text-2); + font-size: 12px; + line-height: 1.5; +} + +@media (prefers-reduced-motion: reduce) { + button.pipe[_ngcontent-%COMP%] { + transition: none; + } +}`]})};function mD(e){let t=e?.pages;return t&&typeof t==`object`?t:{}}function hD(e){let t=x(Ss),n=T({}),r=null,i=null,a=async e=>{i?.(),i=null,r=e;try{let a=await e.scope(`pangular`).rpc.sharedState(`page-angular`);if(t.destroyed||r!==e)return;let o=e=>n.set(mD(e));o(a.value()),i=a.on(`updated`,o)}catch{r===e&&n.set({})}};return E(()=>{let t=e();t&&t!==r&&Z(()=>void a(t))}),t.onDestroy(()=>i?.()),n.asReadonly()}function gD(e,t){if(t)return e[t]??null;let n=null;for(let t of Object.values(e))(!n||t.reportedAt>n.reportedAt)&&(n=t);return n}var _D=[`*`];function vD(e,t){if(e&1&&(R(0,`div`,1)(1,`p`,2),K(2),z(),R(3,`p`,3),K(4),jg(5),R(6,`a`,4),K(7,`Supported versions`),z()()()),e&2){let e=W();G(`compact`,e.compact()),D(2),q(e.title()),D(2),J(` `,t,` `),D(2),V(`href`,e.docsUrl,Du)}}var yD=`https://pangular-inspector.dev/getting-started/installation/#supported-angular-versions`,bD=class e{page=$(null);capability=$.required();missing=$(!1,{transform:bv});compact=$(!1,{transform:bv});docsUrl=yD;text=X(()=>{let e=this.page();return!this.missing()&&!tT(e,this.capability())?null:nT(this.capability(),e?.version)});title=X(()=>{let e=this.page()?.version;return e?`Not available on Angular ${Qw(e)}`:`Not available on this page`});static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-angular-unavailable`]],inputs:{page:[1,`page`],capability:[1,`capability`],missing:[1,`missing`],compact:[1,`compact`]},ngContentSelectors:_D,decls:1,vars:1,consts:[[`role`,`status`,1,`unavailable`,3,`compact`],[`role`,`status`,1,`unavailable`],[1,`title`],[1,`hint`],[`target`,`_blank`,`rel`,`noopener noreferrer`,3,`href`]],template:function(e,t){if(e&1&&(Ag(),A(0,vD,8,5,`div`,0)),e&2){let e;j((e=t.text())?0:-1,e)}},styles:[`[_nghost-%COMP%] { + display: block; +} + +[_nghost-%COMP%]:empty { + display: none; +} + +.unavailable[_ngcontent-%COMP%] { + display: flex; + flex-direction: column; + gap: 6px; + margin: 0 0 12px; + padding: 16px; + border: 1px solid var(--%NS%border); + border-radius: var(--%NS%radius); + background: var(--%NS%surface); + border-style: dashed; +} + +.unavailable.compact[_ngcontent-%COMP%] { + padding: 8px 12px; +} + +.title[_ngcontent-%COMP%] { + margin: 0; + color: var(--%NS%text-strong); + font-size: 14px; + font-weight: 600; +} + +.hint[_ngcontent-%COMP%] { + margin: 0; + color: var(--%NS%text-2); + font-size: 13px; + line-height: 1.55; + overflow-wrap: anywhere; +} + +a[_ngcontent-%COMP%] { + color: var(--%NS%accent); + border-radius: 2px; +} + +a[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +}`]})};function xD(e){return e?.platform===`angular-native`}function SD(e){return xD(e)||e?.platform===`nativescript`}function CD(e,t){let n=Object.values(e??{}).filter(xD);if(t&&n.some(e=>e.pageId===t))return t;let r=null;for(let e of n)(!r||e.reportedAt>r.reportedAt)&&(r=e);return r?.pageId??null}var wD=[`trigger`],TD=[`list`],ED=(e,t)=>t.value;function DD(e,t){if(e&1&&(R(0,`span`,4),K(1),z()),e&2){let e=W();D(),q(e.current().hint)}}function OD(e,t){if(e&1&&(R(0,`span`,15),K(1),z()),e&2){let e=W().$implicit;D(),q(e.hint)}}function kD(e,t){if(e&1){let e=B();R(0,`li`,11),U(`pointerenter`,function(){let t=S(e).$index;return C(W(2).active.set(t))})(`pointerdown`,function(e){return e.preventDefault()})(`click`,function(){let t=S(e).$index;return C(W(2).choose(t))}),w(),R(1,`svg`,12),fg(2,`path`,13),z(),fs(),R(3,`span`,14),K(4),z(),A(5,OD,2,1,`span`,15),z()}if(e&2){let e=t.$implicit,n=t.$index,r=W(2);G(`active`,n===r.active())(`selected`,e.value===r.value()),k(`id`,r.uid+`-opt-`+n)(`aria-selected`,e.value===r.value())(`aria-disabled`,e.disabled||null),D(4),q(e.label),D(),j(e.hint?5:-1)}}function AD(e,t){if(e&1&&(R(0,`li`,10),K(1),z()),e&2){let e=W(2);D(),q(e.emptyText())}}function jD(e,t){if(e&1&&(R(0,`ul`,8,1),Hh(`list-in`),U(`pointerdown`,function(e){return e.preventDefault()}),M(2,kD,6,9,`li`,9,ED,!1,AD,2,1,`li`,10),z()),e&2){let e=W();G(`up`,e.dropUp()),k(`id`,e.uid+`-list`)(`aria-labelledby`,e.labelledBy()||e.uid+`-trigger`),D(2),N(e.options())}}var MD=0,ND=class e{options=$.required();value=mv(null);placeholder=$(`Select…`);ariaLabel=$(``);labelledBy=$(``);describedBy=$(``);disabled=$(!1);emptyText=$(`Nothing to choose`);uid=`app-select-${++MD}`;open=T(!1);active=T(-1);dropUp=T(!1);current=X(()=>this.options().find(e=>e.value===this.value())??null);host=x(Ml);injector=x(bs);trigger=gv.required(`trigger`);list=gv(`list`);typed=``;typedAt=0;toggle(){this.open()?this.close():this.show()}choose(e){let t=this.options()[e];t&&!t.disabled&&(this.value.set(t.value),this.close(),this.trigger().nativeElement.focus())}onKey(e){let t=this.options(),n=this.open();switch(e.key){case`ArrowDown`:case`ArrowUp`:if(e.preventDefault(),!n){this.show();return}this.move(e.key===`ArrowDown`?1:-1);return;case`Home`:case`End`:if(!n)return;e.preventDefault(),this.setActive(e.key===`Home`?this.firstEnabled(0,1):this.firstEnabled(t.length-1,-1));return;case`Enter`:case` `:e.preventDefault(),n&&this.active()>=0?this.choose(this.active()):this.show();return;case`Escape`:if(!n)return;e.preventDefault(),e.stopPropagation(),this.close();return;case`Tab`:n&&this.close();return;default:e.key.length===1&&!e.metaKey&&!e.ctrlKey&&!e.altKey&&this.typeAhead(e.key)}}onBlur(e){let t=e.relatedTarget;t&&this.host.nativeElement.contains(t)||this.close()}onOutside(e){this.open()&&!this.host.nativeElement.contains(e.target)&&this.close()}show(){if(this.disabled())return;let e=this.host.nativeElement.getBoundingClientRect(),t=window.innerHeight-e.bottom;this.dropUp.set(t<220&&e.top>t);let n=this.options().findIndex(e=>e.value===this.value());this.open.set(!0),this.setActive(n>=0?n:this.firstEnabled(0,1))}close(){this.open.set(!1),this.active.set(-1)}move(e){let t=this.firstEnabled(this.active()+e,e);t>=0&&this.setActive(t)}firstEnabled(e,t){let n=this.options();for(let r=e;r>=0&&r{(this.list()?.nativeElement.querySelector(`#${this.uid}-opt-${e}`))?.scrollIntoView({block:`nearest`})},{injector:this.injector})}typeAhead(e){let t=performance.now();this.typed=t-this.typedAt>600?e.toLowerCase():this.typed+e.toLowerCase(),this.typedAt=t;let n=this.options().findIndex(e=>!e.disabled&&e.label.toLowerCase().startsWith(this.typed));n<0||(this.open()?this.setActive(n):this.value.set(this.options()[n].value))}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-select`]],viewQuery:function(e,t){e&1&&Ng(t.trigger,wD,5)(t.list,TD,5),e&2&&Pg(2)},hostAttrs:[1,`app-select`],hostVars:2,hostBindings:function(e,t){e&1&&H(`pointerdown`,function(e){return t.onOutside(e)},ku),e&2&&G(`open`,t.open())},inputs:{options:[1,`options`],value:[1,`value`],placeholder:[1,`placeholder`],ariaLabel:[1,`ariaLabel`],labelledBy:[1,`labelledBy`],describedBy:[1,`describedBy`],disabled:[1,`disabled`],emptyText:[1,`emptyText`]},outputs:{value:`valueChange`},decls:8,vars:13,consts:[[`trigger`,``],[`list`,``],[`type`,`button`,`role`,`combobox`,`aria-haspopup`,`listbox`,1,`trigger`,3,`click`,`keydown`,`blur`,`disabled`],[1,`value`],[1,`value-hint`],[`viewBox`,`0 0 24 24`,`aria-hidden`,`true`,1,`chevron`],[`d`,`m6 9 6 6 6-6`],[`role`,`listbox`,`tabindex`,`-1`,1,`list`,3,`up`],[`role`,`listbox`,`tabindex`,`-1`,1,`list`,3,`pointerdown`],[`role`,`option`,3,`active`,`selected`],[`role`,`option`,`aria-disabled`,`true`,1,`empty`],[`role`,`option`,3,`pointerenter`,`pointerdown`,`click`],[`viewBox`,`0 0 24 24`,`aria-hidden`,`true`,1,`check`],[`d`,`m5 12 5 5 9-10`],[1,`label`],[1,`hint`]],template:function(e,t){e&1&&(R(0,`button`,2,0),U(`click`,function(){return t.toggle()})(`keydown`,function(e){return t.onKey(e)})(`blur`,function(e){return t.onBlur(e)}),R(2,`span`,3),K(3),z(),A(4,DD,2,1,`span`,4),w(),R(5,`svg`,5),fg(6,`path`,6),z()(),A(7,jD,5,5,`ul`,7)),e&2&&(V(`disabled`,t.disabled()),k(`id`,t.uid+`-trigger`)(`aria-controls`,t.uid+`-list`)(`aria-expanded`,t.open())(`aria-label`,t.ariaLabel()||null)(`aria-labelledby`,t.labelledBy()||null)(`aria-describedby`,t.describedBy()||null)(`aria-activedescendant`,t.open()&&t.active()>=0?t.uid+`-opt-`+t.active():null),D(2),G(`placeholder`,!t.current()),D(),J(` `,t.current()?.label??t.placeholder(),` `),D(),j(t.current()?.hint?4:-1),D(3),j(t.open()?7:-1))},styles:[`[_nghost-%COMP%] { + position: relative; + display: inline-flex; + min-width: 0; + vertical-align: middle; +} + +.trigger[_ngcontent-%COMP%] { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + min-width: 0; + height: var(--%NS%control-h, 34px); + padding: 0 10px 0 12px; + border: 1px solid var(--%NS%control-border); + border-radius: var(--%NS%radius-sm); + background: var(--%NS%bg); + color: var(--%NS%text); + font: inherit; + font-size: 13px; + text-align: left; + cursor: pointer; + transition: border-color 150ms var(--%NS%ease), box-shadow 150ms var(--%NS%ease), background-color 150ms var(--%NS%ease); +} + +.trigger[_ngcontent-%COMP%]:hover:not(:disabled) { + border-color: color-mix(in srgb, var(--%NS%control-border) 55%, var(--%NS%text-3)); +} + +.trigger[_ngcontent-%COMP%]:focus-visible, +.open[_nghost-%COMP%] .trigger[_ngcontent-%COMP%] { + outline: none; + border-color: var(--%NS%accent); + box-shadow: 0 0 0 3px var(--%NS%accent-soft); +} + +.trigger[_ngcontent-%COMP%]:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.value[_ngcontent-%COMP%] { + flex: 1 1 auto; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.value.placeholder[_ngcontent-%COMP%] { + color: var(--%NS%text-3); +} + +.value-hint[_ngcontent-%COMP%] { + flex: 0 1 auto; + min-width: 0; + overflow: hidden; + color: var(--%NS%text-3); + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.chevron[_ngcontent-%COMP%] { + flex: none; + width: 14px; + height: 14px; + fill: none; + stroke: var(--%NS%text-3); + stroke-width: 2.2; + stroke-linecap: round; + stroke-linejoin: round; + transition: transform 150ms var(--%NS%ease); +} + +.open[_nghost-%COMP%] .chevron[_ngcontent-%COMP%] { + transform: rotate(180deg); + stroke: var(--%NS%accent); +} + +.list[_ngcontent-%COMP%] { + position: absolute; + top: calc(100% + 6px); + left: 0; + z-index: 50; + box-sizing: border-box; + min-width: 100%; + max-width: min(480px, 100vw - 24px); + max-height: 280px; + margin: 0; + padding: 4px; + overflow: auto; + list-style: none; + border: 1px solid var(--%NS%border-strong); + border-radius: var(--%NS%radius-sm); + background: var(--%NS%surface-2); + box-shadow: 0 16px 40px -12px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(0, 0, 0, 0.2); + overscroll-behavior: contain; +} +@media (prefers-color-scheme: light) { + :root:not([data-theme=dark]) [_nghost-%COMP%] .list[_ngcontent-%COMP%] { + box-shadow: 0 12px 32px -12px rgba(0, 0, 0, 0.25); + } +} +:root[data-theme=light] [_nghost-%COMP%] .list[_ngcontent-%COMP%] { + box-shadow: 0 12px 32px -12px rgba(0, 0, 0, 0.25); +} + +.list.up[_ngcontent-%COMP%] { + top: auto; + bottom: calc(100% + 6px); +} + +.list-in[_ngcontent-%COMP%] { + animation: _ngcontent-%COMP%_list-in 140ms var(--%NS%ease) both; +} + +@keyframes _ngcontent-%COMP%_list-in { + from { + opacity: 0; + transform: translateY(-4px) scale(0.98); + } +} +li[_ngcontent-%COMP%] { + display: flex; + align-items: center; + gap: 8px; + min-height: 32px; + padding: 0 10px 0 8px; + border-radius: 6px; + color: var(--%NS%text); + font-size: 13px; + cursor: pointer; + white-space: nowrap; +} + +li.active[_ngcontent-%COMP%] { + background: var(--%NS%surface-3); +} + +li.selected[_ngcontent-%COMP%] { + color: var(--%NS%text-strong); + font-weight: 600; +} + +li[aria-disabled=true][_ngcontent-%COMP%] { + color: var(--%NS%text-3); + cursor: default; +} + +.check[_ngcontent-%COMP%] { + flex: none; + width: 14px; + height: 14px; + fill: none; + stroke: var(--%NS%accent); + stroke-width: 2.4; + stroke-linecap: round; + stroke-linejoin: round; + visibility: hidden; +} + +li.selected[_ngcontent-%COMP%] .check[_ngcontent-%COMP%] { + visibility: visible; +} + +.label[_ngcontent-%COMP%] { + flex: 1 1 auto; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} + +.hint[_ngcontent-%COMP%] { + flex: none; + max-width: 50%; + overflow: hidden; + color: var(--%NS%text-3); + font-size: 12px; + font-weight: 400; + text-overflow: ellipsis; +} + +.empty[_ngcontent-%COMP%] { + justify-content: center; +} + +@media (prefers-reduced-motion: reduce) { + .list-in[_ngcontent-%COMP%] { + animation: none; + } + .chevron[_ngcontent-%COMP%] { + transition: none; + } +}`]})};function PD(e,t){let n=Jw(t);if(Yw(n))return{nodes:e,matches:0};let r=0,i=e=>e.flatMap(e=>{let t=Xw(e,n);t&&r++;let a=i(e.children);return t||a.length?[{...e,children:a}]:[]});return{nodes:i(e),matches:r}}function FD(e){return e.flatMap(e=>e.kind===`directive`?FD(e.children):[{...e,children:FD(e.children)}])}function ID(e){return(e.attributes?.length?e.attributes:e.directives??[e.name]).map(e=>`[${e}]`).join(` `)}var LD={hydrated:`Hydrated`,skipped:`Skipped`,mismatched:`Mismatch`,client:`Client`};function RD(e){return LD[e]}function zD(e,t,n,r=!1,i=0){let a=r?`${e}+`:`${e}`;if(n)return`${t} of ${r?`${e+i}+`:e+i}`;let o=i?` · ${i} directive ${i===1?`host`:`hosts`}`:``;return`${a} ${e===1&&!r?`instance`:`instances`}${o}`}function BD(e,t,n,r=!1,i=0){if(!n.trim())return``;if(!t)return`No components match.`;let a=e+i,o=r?`${a}+`:`${a}`,s=!r&&a===1;return`${t} of ${o} ${i?s?`row`:`rows`:s?`instance`:`instances`} match.`}function VD(e){let t=e?.components,n=e?.depth;return t&&n?`Showing the first ${t} component instances, and none nested more than ${n} elements deep. Others are not listed or searchable.`:t?`Showing the first ${t} component instances. Others are not listed or searchable.`:n?`Components nested more than ${n} elements deep are not listed or searchable.`:`The page has more components than the tree shows.`}function HD(e,t){let n=t?.detail?.id??null,r=t?.pageId??null;if(r!==e.pageId)return{pageId:r,selectedId:n,detailId:n,destroyed:null};let i=e.selectedId;return n&&n!==e.detailId&&n!==i&&(i=n),i&&t&&!t.truncated&&!UD(t.roots,i)?{pageId:r,selectedId:null,detailId:n,destroyed:i}:{pageId:r,selectedId:i,detailId:n,destroyed:null}}function UD(e,t){return e.some(e=>e.id===t||UD(e.children,t))}function WD(e,t,n){let r=e.indexOf(n);if(r<0)return null;for(let n=r+1;n=0;n--)if(t.has(e[n]))return e[n];return null}var GD=(e,t)=>t.key,KD=(e,t)=>t.id;function qD(e,t){e&1&&(R(0,`p`,2),K(1,` The HTML came from the server, but no component was hydrated, so the app rendered again on the client. Add `),R(2,`code`),K(3,`provideClientHydration()`),z(),K(4,` and use a development build to see each component's status. `),z())}function JD(e,t){if(e&1&&(R(0,`li`)(1,`span`,9),K(2),z(),K(3),z()),e&2){let e=t.$implicit;r_(`count count-`+e.key),D(2),q(e.value),D(),J(` `,e.label,` `)}}function YD(e,t){if(e&1&&(R(0,`span`,15),K(1,`Expected `),R(2,`code`),K(3),z()()),e&2){let e=W().$implicit;D(3),q(e.expected)}}function XD(e,t){if(e&1&&(R(0,`span`,15),K(1,`Found `),R(2,`code`),K(3),z()()),e&2){let e=W().$implicit;D(3),q(e.actual)}}function ZD(e,t){if(e&1){let e=B();R(0,`li`)(1,`button`,12),U(`click`,function(){let t=S(e).$implicit;return C(W(4).showNode.emit(t.id))})(`focus`,function(){let t=S(e).$implicit;return C(W(4).highlight.emit(t.id))})(`blur`,function(){return S(e),C(W(4).highlight.emit(null))})(`mouseenter`,function(){let t=S(e).$implicit;return C(W(4).highlight.emit(t.id))})(`mouseleave`,function(){return S(e),C(W(4).highlight.emit(null))}),R(2,`span`,13),K(3),z(),R(4,`span`,14),K(5),z(),A(6,YD,4,1,`span`,15),A(7,XD,4,1,`span`,15),z()()}if(e&2){let e=t.$implicit;D(3),q(e.name),D(2),J(`<`,e.tag,`>`),D(),j(e.expected?6:-1),D(),j(e.actual?7:-1)}}function QD(e,t){if(e&1&&(R(0,`h3`,10),K(1,`Mismatches`),z(),R(2,`ul`,11),M(3,ZD,8,4,`li`,null,KD),z()),e&2){let e=W(2);D(3),N(e.mismatches)}}function $D(e,t){if(e&1){let e=B();R(0,`div`,3)(1,`ul`,4),M(2,JD,4,4,`li`,5,GD),z(),R(4,`button`,6),U(`click`,function(){return S(e),C(W(2).toggleOverlay())}),w(),R(5,`svg`,7),fg(6,`rect`,8),z(),K(7,` Outline on page `),z()(),A(8,QD,5,0)}if(e&2){let e=W(),t=W();D(2),N(t.counts()),D(2),k(`aria-pressed`,t.overlayOn()),D(4),j(e.mismatches.length?8:-1)}}function eO(e,t){e&1&&A(0,qD,5,0,`p`,2)(1,$D,9,2),e&2&&j(+!!t.hydrated)}function tO(e,t){e&1&&(R(0,`p`,2),K(1,` This page was not rendered on the server, so there is nothing to hydrate. With server-side rendering and `),R(2,`code`),K(3,`provideClientHydration()`),z(),K(4,`, each component shows whether it was hydrated, skipped or mismatched. `),z())}var nO=class e{report=$(void 0);overlay=lv();showNode=lv();highlight=lv();overlayOn=Q(()=>this.report()?.overlay===!0);counts=X(()=>{let e=this.report()?.counts;return e?[{key:`hydrated`,label:`hydrated`,value:e.hydrated},{key:`skipped`,label:`skipped`,value:e.skipped},{key:`mismatched`,label:`mismatched`,value:e.mismatched},{key:`client`,label:`created on the client`,value:e.client},{key:`dehydratedBlocks`,label:e.dehydratedBlocks===1?`dehydrated @defer block`:`dehydrated @defer blocks`,value:e.dehydratedBlocks}]:[]});toggleOverlay(){let e=!this.overlayOn();this.overlayOn.set(e),this.overlay.emit(e)}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-hydration-section`]],inputs:{report:[1,`report`]},outputs:{overlay:`overlay`,showNode:`showNode`,highlight:`highlight`},decls:5,vars:1,consts:[[`aria-labelledby`,`ct-hydration-title`,1,`hydration`],[`id`,`ct-hydration-title`,1,`title`],[1,`empty`],[1,`summary`],[`aria-label`,`Hydration summary`,1,`counts`],[3,`class`],[`type`,`button`,1,`outline`,3,`click`],[`viewBox`,`0 0 24 24`,`aria-hidden`,`true`],[`x`,`3`,`y`,`3`,`width`,`18`,`height`,`18`,`rx`,`2`,`stroke-dasharray`,`4 3`],[1,`num`],[1,`label`],[1,`mismatches`],[`type`,`button`,1,`mismatch`,3,`click`,`focus`,`blur`,`mouseenter`,`mouseleave`],[1,`name`,`mono`],[1,`mono`,`muted`],[1,`detail-line`]],template:function(e,t){if(e&1&&(R(0,`section`,0)(1,`h2`,1),K(2,`Hydration`),z(),A(3,eO,2,1)(4,tO,5,0,`p`,2),z()),e&2){let e;D(3),j((e=t.report())?3:4,e)}},styles:[`.hydration[_ngcontent-%COMP%] { + margin-top: 16px; +} + +.title[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + margin: 0 0 8px; +} + +.label[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + margin: 12px 0 6px; +} + +.empty[_ngcontent-%COMP%] { + margin: 0; + padding: 10px 12px; + border: 1px dashed var(--%NS%border); + border-radius: var(--%NS%radius-sm); + color: var(--%NS%text-2); + line-height: 1.5; +} + +code[_ngcontent-%COMP%], +.mono[_ngcontent-%COMP%] { + font-family: var(--%NS%font-mono); +} + +code[_ngcontent-%COMP%] { + font-size: 12px; + overflow-wrap: anywhere; +} + +.muted[_ngcontent-%COMP%] { + color: var(--%NS%text-2); +} + +.summary[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px 12px; +} + +.counts[_ngcontent-%COMP%] { + display: flex; + flex: 1 1 auto; + flex-wrap: wrap; + gap: 6px; + margin: 0; + padding: 0; + list-style: none; +} + +.count[_ngcontent-%COMP%] { + padding: 2px 10px; + border: 1px solid var(--%NS%border-strong); + border-radius: 99px; + color: var(--%NS%text); + font-size: 12px; +} + +.num[_ngcontent-%COMP%] { + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +.count-hydrated[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%ok) 30%, transparent); + background: color-mix(in srgb, var(--%NS%ok) 12%, transparent); + color: var(--%NS%ok-text); +} + +.count-skipped[_ngcontent-%COMP%], +.count-dehydratedBlocks[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%warn) 30%, transparent); + background: color-mix(in srgb, var(--%NS%warn) 12%, transparent); + color: var(--%NS%warn-text); +} + +.count-mismatched[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%danger) 30%, transparent); + background: color-mix(in srgb, var(--%NS%danger) 12%, transparent); + color: var(--%NS%danger-text); +} + +button[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; + gap: 8px; + height: var(--%NS%control-h); + padding: 0 14px; + background: var(--%NS%surface-2); + border: 1px solid var(--%NS%control-border); + border-radius: var(--%NS%radius-sm); + color: var(--%NS%text); + cursor: pointer; + font: inherit; + font-size: 13px; + font-weight: 500; + transition: background-color 150ms var(--%NS%ease), border-color 150ms var(--%NS%ease); +} + +button[_ngcontent-%COMP%]:hover { + background: var(--%NS%surface-3); + border-color: var(--%NS%accent-line); +} + +button[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.outline[aria-pressed=true][_ngcontent-%COMP%] { + background: var(--%NS%accent-soft); + border-color: var(--%NS%accent-line); + color: var(--%NS%text-strong); +} + +svg[_ngcontent-%COMP%] { + width: 14px; + height: 14px; + fill: none; + stroke: currentColor; + stroke-width: 2.2; +} + +.mismatches[_ngcontent-%COMP%] { + display: grid; + gap: 6px; + margin: 0; + padding: 0; + list-style: none; +} + +.mismatch[_ngcontent-%COMP%] { + flex-wrap: wrap; + justify-content: flex-start; + width: 100%; + height: auto; + min-height: var(--%NS%control-h); + padding: 6px 12px; + background: var(--%NS%surface); + border-color: var(--%NS%border); + text-align: left; +} + +.name[_ngcontent-%COMP%] { + color: var(--%NS%text-strong); + font-size: 12px; + font-weight: 600; +} + +.detail-line[_ngcontent-%COMP%] { + width: 100%; + color: var(--%NS%text-2); + font-size: 12px; +} + +@media (prefers-reduced-motion: reduce) { + button[_ngcontent-%COMP%] { + transition: none; + } +}`]})};function rO(e,t){if(e&1&&(R(0,`p`,0),K(1),R(2,`code`),K(3),z(),K(4,` to keep more. `),R(5,`a`,1),K(6,`About limits`),z()()),e&2){let e=W();D(),k_(` Showing the latest `,e.max(),` `,e.what(),`. `,e.dropped(),` older `,e.dropped()===1?`entry was`:`entries were`,` dropped. Raise `),D(2),J(`limits.`,e.limit()),D(2),V(`href`,e.docsUrl,Du)}}var iO=`https://pangular-inspector.dev/getting-started/configuration/#limits`,aO=class e{dropped=$(0);max=$.required();what=$.required();limit=$.required();docsUrl=iO;static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-limit-note`]],inputs:{dropped:[1,`dropped`],max:[1,`max`],what:[1,`what`],limit:[1,`limit`]},decls:1,vars:1,consts:[[1,`note`],[`target`,`_blank`,`rel`,`noopener noreferrer`,3,`href`]],template:function(e,t){e&1&&A(0,rO,7,6,`p`,0),e&2&&j(t.dropped()>0?0:-1)},styles:[`[_nghost-%COMP%] { + display: block; +} + +.note[_ngcontent-%COMP%] { + margin: 0 0 12px; + padding: 8px 12px; + border: 1px solid var(--%NS%border); + border-radius: var(--%NS%radius-sm); + background: var(--%NS%surface); + color: var(--%NS%text-2); + line-height: 1.5; +} + +code[_ngcontent-%COMP%] { + font-family: var(--%NS%font-mono); + color: var(--%NS%text); +} + +a[_ngcontent-%COMP%] { + color: var(--%NS%accent); + border-radius: 2px; +} + +a[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +}`]})},oO=1e3;function sO(e){return typeof e==`number`&&Number.isFinite(e)&&e>0&&e<=oO?e:16}var cO=864e13,lO=200,uO=300,dO=100,fO=10,pO=100,mO=2e3,hO=2e3,gO=4e3,_O=1e3,vO=100,yO=3,bO=50,xO={component:`c`,hook:`h`,afterRender:`r`,output:`o`},SO=Object.values(xO),CO=e=>!!e&&typeof e==`object`&&!Array.isArray(e),wO=e=>typeof e==`number`&&Number.isFinite(e)&&e>=0?e:0,TO=(e,t)=>typeof e==`string`?e.slice(0,t):``,EO=e=>wO(e)>0?wO(e):void 0,DO=(e,t)=>Array.isArray(e)?e.slice(0,t):[];function OO(e){if(!CO(e))return null;let t=TO(e.name,lO);return t?{name:t,checks:wO(e.checks),ms:wO(e.ms)}:null}function kO(e,t){if(!Array.isArray(e)||e.length!==5)return null;let[n,r,i,a,o]=e;return typeof n!=`number`||!Number.isInteger(n)||n<0||n>=t||typeof r!=`number`||!Number.isFinite(r)||Math.abs(r)>1e7||typeof a!=`number`||!Number.isInteger(a)||a<0||a>_O||typeof o!=`string`||!SO.includes(o)?null:[n,r,wO(i),a,o]}function AO(e,t,n){if(!CO(e)||typeof e.id!=`number`)return null;let r=DO(e.components,fO).map(OO).filter(e=>!!e),i=TO(e.trigger,uO),a=DO(e.spans,40).map(e=>kO(e,t)).filter(e=>!!e),o=Array.isArray(e.spans)?e.spans.length-40:0,s=Math.floor(wO(e.droppedSpans))+Math.max(0,o),c={t:typeof e.t==`number`?wO(e.t):void 0,hookMs:EO(e.hookMs),afterRenderMs:EO(e.afterRenderMs),outputMs:EO(e.outputMs)},l=PO(e.cause),u=EO(e.refreshed),d=EO(e.unchanged);return{id:wO(e.id),at:wO(e.at),ms:wO(e.ms),passes:wO(e.passes),...i?{trigger:i}:{},checks:wO(e.checks),components:r,...Object.fromEntries(Object.entries(c).filter(([,e])=>e!==void 0)),...a.length?{spans:a}:{},...s?{droppedSpans:s}:{},...l?{cause:l}:{},...u?{refreshed:u}:{},...d?{unchanged:d}:{},...wO(e.ms)>n?{slow:!0}:{}}}var jO=[`event`,`http`,`timer`,`signal`,`effect`,`markForCheck`,`input`,`view`,`tick`,`zone`,`unknown`],MO={event:`Event`,http:`HTTP response`,timer:`Timer`,signal:`Signal write`,effect:`Effect`,markForCheck:`markForCheck`,input:`setInput`,view:`View change`,tick:`ApplicationRef.tick`,zone:`zone.js only`,unknown:`Unknown`},NO=e=>typeof e==`string`&&jO.includes(e);function PO(e){if(!CO(e)||!NO(e.kind))return;let t=[`component`,`listener`,`event`,`target`,`signal`,`task`],n={kind:e.kind};NO(e.via)&&(n.via=e.via);for(let r of t){let t=TO(e[r],lO);t&&(n[r]=t)}e.zone===!0&&(n.zone=!0);let r=DO(e.stack,yO).map(e=>TO(e,uO)).filter(Boolean);r.length&&(n.stack=r);let i=Math.floor(wO(e.more));return i&&(n.more=i),n}function FO(e){if(!CO(e))return null;let t=TO(e.name,lO);if(!t)return null;let n=e.strategy;return{name:t,refreshed:Math.floor(wO(e.refreshed)),unchanged:Math.floor(wO(e.unchanged)),...n===`OnPush`||n===`Eager`?{strategy:n}:{}}}function IO(e){if(!CO(e))return null;let t=TO(e.name,lO),n=TO(e.input,lO);if(!t||!n)return null;let r=TO(e.parent,lO);return{name:t,input:n,...r?{parent:r}:{},inPlace:Math.floor(wO(e.inPlace)),sameContents:Math.floor(wO(e.sameContents))}}function LO(e){if(!CO(e))return null;let t=TO(e.name,lO);if(!t)return null;let n=DO(e.tasks,3).map(e=>TO(e,lO)).filter(Boolean);return{name:t,ticks:Math.floor(wO(e.ticks)),...n.length?{tasks:n}:{}}}function RO(e){if(!CO(e))return;let t={};if(CO(e.causes))for(let n of jO){let r=Math.floor(wO(e.causes[n]));r&&(t[n]=r)}let n=DO(e.components,pO).map(FO).filter(e=>!!e),r=DO(e.inputs,bO).map(IO).filter(e=>!!e),i=DO(e.zone,bO).map(LO).filter(e=>!!e),a=Math.floor(wO(e.major));return{triggers:e.triggers===!0,waste:e.waste===!0,...a&&a<1e3?{major:a}:{},causes:t,components:n,...r.length?{inputs:r}:{},...i.length?{zone:i}:{},...typeof e.zoneJs==`boolean`?{zoneJs:e.zoneJs}:{}}}function zO(e){let t=[],n=e.component?` in ${e.component}${e.listener?` (${e.listener})`:``}`:``;switch(e.kind){case`event`:t.push(`${e.event??`Template listener`}${e.target?` on ${e.target}`:``}${n}`);break;case`http`:t.push(`HTTP response${n}`);break;case`timer`:t.push(`${e.task??`Timer`}${n}`);break;case`zone`:t.push(`zone.js tick with no Angular notification${e.task?` after ${e.task}`:``}`);break;case`tick`:t.push(`ApplicationRef.tick()${n}`);break;default:t.push(`${MO[e.kind]}${n}`)}return e.signal?t.push(e.signal===`?`?`wrote an unnamed signal`:`wrote signal ${e.signal}`):e.via&&e.via!==e.kind&&t.push(`via ${MO[e.via]}`),e.zone&&e.kind!==`zone`&&t.push(`run by zone.js`),e.more&&t.push(`${e.more} more notification${e.more===1?``:`s`}`),t.join(`, `)}function BO(e){if(!e)return[];let t=[],n=(e.major??0)>=22;for(let r of e.components){if(r.refreshed<10)continue;let e=r.refreshed-r.unchanged;if(e/r.refreshed>.2)continue;let i=`refreshed ${r.refreshed} times and changed something in ${e}`;r.strategy===`Eager`?t.push({name:r.name,kind:`on-push`,text:n?`${r.name} was ${i}. It opts out of OnPush, which is the default from Angular 22: remove \`changeDetection: ChangeDetectionStrategy.Eager\` (or \`Default\`) so Angular skips it until an input, event, signal or markForCheck marks it.`:`${r.name} was ${i}. Add \`changeDetection: ChangeDetectionStrategy.OnPush\` so Angular skips it until an input, event, signal or markForCheck marks it.`}):r.strategy===`OnPush`&&t.push({name:r.name,kind:`already-on-push`,text:`${r.name} was ${i}, and it is already OnPush${n?` (the default from Angular 22)`:``}. Something keeps marking it: an input that gets a new reference each time, an event or output in it, markForCheck, or a signal it reads that is set to an equal value.`})}for(let n of e.inputs??[]){let e=n.parent?` set by ${n.parent}`:``;n.inPlace&&t.push({name:n.name,kind:`in-place`,text:`Input ${n.input} of ${n.name}${e} changed in place ${n.inPlace} time${n.inPlace===1?``:`s`}: same reference, different contents. OnPush components, computed and signal equality don't see that. Pass a new object or array instead of mutating it.`}),n.sameContents&&t.push({name:n.name,kind:`same-contents`,text:`Input ${n.input} of ${n.name}${e} got a new reference with the same contents ${n.sameContents} time${n.sameContents===1?``:`s`}, so Angular refreshed ${n.name} for nothing. Keep the reference when nothing changed, for example with a computed instead of a method call in the template.`})}for(let n of e.zone??[]){let e=n.tasks?.length?` (after ${n.tasks.join(`, `)})`:``;t.push({name:n.name,kind:`zone`,text:`${n.name} changed in ${n.ticks} tick${n.ticks===1?``:`s`} that only zone.js started${e}. Without zone.js it would not update: keep that state in a signal, or call markForCheck after changing it.`})}return t}function VO(e){let t=OO(e);return!t||!CO(e)?null:{...t,maxMs:wO(e.maxMs),cycles:wO(e.cycles)}}function HO(e){if(!CO(e))return null;let t=TO(e.name,lO),n=TO(e.hook,dO);if(!t||!n)return null;let{calls:r,ms:i,maxMs:a}=e;return{name:t,hook:n,calls:wO(r),ms:wO(i),maxMs:wO(a)}}function UO(e){if(!CO(e))return null;let t=TO(e.name,lO);if(!t)return null;let n=TO(e.listener,lO);return{name:t,...n?{listener:n}:{},calls:wO(e.calls),ms:wO(e.ms),maxMs:wO(e.maxMs)}}function WO(e){if(CO(e)&&wO(e.runs))return{runs:wO(e.runs),ms:wO(e.ms),maxMs:wO(e.maxMs)}}function GO(e,t){if(!CO(e))return null;let n=DO(e.spanNames,gO).map(e=>TO(e,uO)),r=sO(e.slowMs),i=Array.isArray(e.cycles)?e.cycles.slice(-Math.min(t,hO)).map(e=>AO(e,n.length,r)).filter(e=>!!e):[],a={},o=0;if(CO(e.hosts)){let t=Object.entries(e.hosts);o=Math.max(0,t.length-mO);for(let[e,n]of t.slice(0,mO))e.length<50&&wO(n)&&(a[e]=wO(n))}let s=Array.isArray(e.components)?e.components:[],c=Math.floor(wO(e.droppedClasses))+Math.max(0,s.length-pO),l=Math.floor(wO(e.droppedHosts))+o,u=DO(e.hooks,vO).map(HO).filter(e=>!!e),d=DO(e.outputs,vO).map(UO).filter(e=>!!e),f=Math.max(0,DO(e.hooks,1/0).length-vO)+Math.max(0,DO(e.outputs,1/0).length-vO),p=Math.floor(wO(e.droppedTimings))+f,m=WO(e.afterRender),ee=i.some(e=>e.spans?.length),h=RO(e.diagnosis);return{recording:e.recording===!0,startedAt:typeof e.startedAt==`number`&&e.startedAt<=cO?wO(e.startedAt):null,dropped:wO(e.dropped),slowMs:r,cycles:i,components:s.slice(0,pO).map(VO).filter(e=>!!e),...c?{droppedClasses:c}:{},hosts:a,...l?{droppedHosts:l}:{},...u.length?{hooks:u}:{},...d.length?{outputs:d}:{},...p?{droppedTimings:p}:{},...m?{afterRender:m}:{},...ee?{spanNames:n}:{},...h?{diagnosis:h}:{}}}var KO=`pangular-inspector/change-detection`;function qO(e,t,n=Date.now()){return{format:KO,version:1,exportedAt:n,pageId:t,recording:{...GO(e,hO)??e,recording:!1,hosts:{}}}}function JO(e,t){if(e.length>2e7||e.length*3>2e7&&new TextEncoder().encode(e).byteLength>2e7)return{ok:!1,error:`The file is larger than 20 MB.`};let n;try{n=JSON.parse(e)}catch{return{ok:!1,error:`The file is not valid JSON.`}}if(!CO(n))return{ok:!1,error:`The file is not a recording.`};if(n.format!==void 0&&n.format!==`pangular-inspector/change-detection`)return{ok:!1,error:`The file is not a change detection recording.`};let r=CO(n.recording)?n.recording:n;if(!Array.isArray(r.cycles))return{ok:!1,error:`The file has no change detection cycles.`};let i=GO(r,t);if(!i)return{ok:!1,error:`The file is not a recording.`};let a=TO(n.pageId??r.pageId,49)||`imported`,o=EO(n.exportedAt);return{ok:!0,pageId:a,...o?{exportedAt:o}:{},recording:{...i,recording:!1}}}var YO=1,XO=1,ZO=2,QO={c:`component`,h:`lifecycle-hook`,r:`after-render`,o:`output`},$O=e=>Math.round(e*1e6)/1e3;function ek(e,t){let n=e.spanNames??[],r=e.cycles[0]?.at??0,i=e.startedAt??r,a=[],o=0,s=0;for(let t of e.cycles){let e=t.t??Math.max(0,t.at-i),r=e+t.ms;a.push({name:`Change detection #${t.id}`,cat:`change-detection`,ph:`X`,pid:YO,tid:XO,ts:$O(e),dur:$O(t.ms),args:{checks:t.checks,passes:t.passes,...t.trigger?{trigger:t.trigger}:{},...t.hookMs?{hookMs:t.hookMs}:{},...t.afterRenderMs?{afterRenderMs:t.afterRenderMs}:{},...t.outputMs?{outputMs:t.outputMs}:{},...t.cause?{cause:zO(t.cause)}:{},...t.unchanged?{unchanged:t.unchanged}:{},slowest:t.components.map(e=>`${e.name} ${e.checks}x ${e.ms}ms`)}}),s+=t.droppedSpans??0;let c=[{end:r}],l=[...t.spans??[]].sort((e,t)=>e[1]-t[1]||t[2]-e[2]);for(let[r,i,s,u,d]of l){o++;let l=n[r]??`?`;if(d===xO.output){let n=e+i;a.push({name:l,cat:QO[d],ph:`X`,pid:YO,tid:ZO,ts:$O(n),dur:$O(i<0?Math.max(0,Math.min(s,-i)):s),args:{cycle:t.id}});continue}let f=Math.max(e,e+i);for(;c.length>1&&c.at(-1).end<=f;)c.pop();let p=c.at(-1).end,m=Math.min(f+s,p);me.ts??0));if(c<0)for(let e of a)e.ts=$O((e.ts-c)/1e3);a.sort((e,t)=>e.ts-t.ts||t.dur-e.dur);let l=(e,t,n)=>({name:t,ph:`M`,pid:YO,tid:e,args:{name:n}});return{traceEvents:[l(0,`process_name`,`Angular change detection (page ${t})`),l(XO,`thread_name`,`Change detection`),l(ZO,`thread_name`,`Outputs`),...a],displayTimeUnit:`ms`,otherData:{source:`Pangular Inspector change detection recording`,pageId:t,cycles:e.cycles.length,droppedCycles:e.dropped,spans:o,droppedSpans:s,...e.startedAt&&e.startedAt<=cO?{startedAt:new Date(e.startedAt).toISOString()}:{}}}}function tk(e,t=16){return e.ms>t}function nk(e){return`${e<10?e.toFixed(2):Math.round(e)} ms`}function rk(e,t){if(!t)return[...e];let n=Math.min(t.from,t.to),r=Math.max(t.from,t.to),i=e.filter(e=>e.id>=n&&e.id<=r);return i.length?i:[...e]}function ik(e){let t=new Map;for(let n of e)for(let e of n.components){let n=t.get(e.name);n?(n.checks+=e.checks,n.ms+=e.ms,n.maxMs=Math.max(n.maxMs,e.ms),n.cycles+=1):t.set(e.name,{name:e.name,checks:e.checks,ms:e.ms,maxMs:e.ms,cycles:1})}return[...t.values()].sort((e,t)=>t.ms-e.ms||e.name.localeCompare(t.name))}function ak(e,t=16){let n=e.reduce((e,t)=>Math.max(e,t.ms),0);return Math.max(n,t*1.25)}function ok(e,t,n=3){let r=e.map(e=>({item:e,value:t(e)})).filter(e=>e.value>0).sort((e,t)=>t.value-e.value),i=r.reduce((e,t)=>e+t.value,0);if(!i)return[];let a=n/i,o=[],s=0,c=0,l=n,u=1,d=[],f=(e,t)=>{let n=e.reduce((e,t)=>e+t,0),r=Math.max(...e),i=Math.min(...e);return Math.max(t*t*r/(n*n),n*n/(t*t*i))},p=()=>{let e=d.reduce((e,t)=>e+t.area,0);if(l>=u){let t=e/u,n=c;for(let e of d){let r=e.area/t;o.push({item:e.item,x:s,y:n,w:t,h:r}),n+=r}s+=t,l-=t}else{let t=e/l,n=s;for(let e of d){let r=e.area/t;o.push({item:e.item,x:n,y:c,w:r,h:t}),n+=r}c+=t,u-=t}d=[]};for(let e of r){let t=e.value*a,n=Math.min(l,u),r=d.map(e=>e.area);d.length&&f([...r,t],n)>f(r,n)&&p(),d.push({item:e.item,area:t})}return d.length&&p(),o.map(e=>({item:e.item,x:e.x/n*100,y:e.y*100,w:e.w/n*100,h:e.h*100}))}var sk={c:`component`,h:`hook`,r:`afterRender`,o:`output`};function ck(e,t){if(t===`c`)return e||null;if(t===`h`){let t=e.lastIndexOf(`.`);return t>0?e.slice(0,t):null}if(t===`o`){let t=e.indexOf(` (`),n=t>0?e.slice(0,t):e;return n&&n!==`?`?n:null}return null}function lk(e,t){let n=e.filter(e=>e.spans?.length);if(!n.length)return null;let r=n.map(e=>{let t=e.spans,n=Math.min(0,...t.map(e=>e[1])),r=Math.max(e.ms,...t.map(e=>e[1]+e[2]));return{cycle:e,spans:t,from:n,width:Math.max(r-n,.001)}}),i=r.reduce((e,t)=>e+t.width,0),a=[],o=[],s=0,c=0;for(let{cycle:e,spans:n,from:l,width:u}of r){o.push({id:e.id,x:s/i*100,w:u/i*100,droppedSpans:e.droppedSpans??0});let r=[...n].sort((e,t)=>e[1]-t[1]||e[3]-t[3]),d=[];for(let[n,o,u,f,p]of r){let r=t[n]??`unknown`,m=d[f-1],ee=f>0&&m!==void 0&&ot.kind,dk=(e,t)=>t.name,fk=(e,t)=>t.name+`.`+t.input;function pk(e,t){e&1&&(R(0,`p`,3),K(1,` The page has no Angular change detection scheduler the overlay could find (it needs Angular 18 or later), so cycles have no cause. `),z())}function mk(e,t){if(e&1&&(R(0,`tr`)(1,`td`),K(2),z(),R(3,`td`,6),K(4),z()()),e&2){let e=t.$implicit;D(2),q(e.label),D(2),q(e.count)}}function hk(e,t){if(e&1&&(R(0,`table`)(1,`thead`)(2,`tr`)(3,`th`,4),K(4,`Trigger`),z(),R(5,`th`,5),K(6,`Cycles`),z()()(),R(7,`tbody`),M(8,mk,5,2,`tr`,null,uk),z()()),e&2){let e=W(2);D(8),N(e.causes())}}function gk(e,t){e&1&&(R(0,`p`,3),K(1,`No cycle has a cause yet.`),z())}function _k(e,t){if(e&1&&(R(0,`tr`)(1,`td`,7),K(2),z(),R(3,`td`,6),K(4),z(),R(5,`td`,7),K(6),z()()),e&2){let e=t.$implicit;D(2),q(e.name),D(2),q(e.ticks),D(2),q(e.tasks?.join(`, `))}}function vk(e,t){if(e&1&&(R(0,`table`)(1,`thead`)(2,`tr`)(3,`th`,4),K(4,`Component`),z(),R(5,`th`,5),K(6,`Zone-only ticks`),z(),R(7,`th`,4),K(8,`After`),z()()(),R(9,`tbody`),M(10,_k,7,3,`tr`,null,dk),z()()),e&2){let e=W(3);D(10),N(e.zone())}}function yk(e,t){if(e&1&&(R(0,`p`,3),K(1),z()),e&2){let e=W(3);D(),q(e.zoneNote())}}function bk(e,t){if(e&1&&(R(0,`div`)(1,`h4`),K(2,`Depends on zone.js`),z(),A(3,vk,12,0,`table`)(4,yk,2,1,`p`,3),z()),e&2){let e=W(),t=W();D(3),j(t.zone().length?3:e.triggers?4:-1)}}function xk(e,t){e&1&&(R(0,`p`,3),K(1,`The page can't watch DOM updates, so wasted checks are not counted.`),z())}function Sk(e,t){if(e&1&&(R(0,`tr`)(1,`td`,7),K(2),z(),R(3,`td`,6),K(4),z(),R(5,`td`,6),K(6),z(),R(7,`td`),K(8),z()()),e&2){let e=t.$implicit,n=W(3);D(2),q(e.name),D(2),q(e.refreshed),D(2),Y(``,e.unchanged,` (`,n.share(e.unchanged,e.refreshed),`)`),D(2),q(e.strategy??`Unknown`)}}function Ck(e,t){if(e&1&&(R(0,`table`)(1,`thead`)(2,`tr`)(3,`th`,4),K(4,`Component`),z(),R(5,`th`,5),K(6,`Refreshed`),z(),R(7,`th`,5),K(8,`Changed nothing`),z(),R(9,`th`,4),K(10,`Strategy`),z()()(),R(11,`tbody`),M(12,Sk,9,5,`tr`,null,dk),z()()),e&2){let e=W(2);D(12),N(e.wasted())}}function wk(e,t){e&1&&(R(0,`p`,3),K(1,`Every refreshed check changed something.`),z())}function Tk(e,t){if(e&1&&(R(0,`tr`)(1,`td`,7),K(2),z(),R(3,`td`,7),K(4),z(),R(5,`td`,6),K(6),z(),R(7,`td`,6),K(8),z()()),e&2){let e=t.$implicit;D(2),Y(``,e.name,`.`,e.input),D(2),q(e.parent??`Unknown`),D(2),q(e.inPlace),D(2),q(e.sameContents)}}function Ek(e,t){if(e&1&&(R(0,`h4`),K(1,`Inputs that break OnPush`),z(),R(2,`table`)(3,`thead`)(4,`tr`)(5,`th`,4),K(6,`Input`),z(),R(7,`th`,4),K(8,`Set by`),z(),R(9,`th`,5),K(10,`Changed in place`),z(),R(11,`th`,5),K(12,`Same contents, new reference`),z()()(),R(13,`tbody`),M(14,Tk,9,5,`tr`,null,fk),z()()),e&2){let e=W(2);D(14),N(e.inputs())}}function Dk(e,t){if(e&1&&(R(0,`li`),K(1),z()),e&2){let e=t.$implicit;D(),q(e.text)}}function Ok(e,t){if(e&1&&(R(0,`h4`),K(1,`Hints`),z(),R(2,`ul`,8),M(3,Dk,2,1,`li`,null,eg),z()),e&2){let e=W(2);D(3),N(e.hints())}}function kk(e,t){if(e&1&&(R(0,`section`,0)(1,`h3`,1),K(2,`Why it ran and what it wasted`),z(),R(3,`div`,2)(4,`div`)(5,`h4`),K(6,`What started the cycles`),z(),A(7,pk,2,0,`p`,3)(8,hk,10,0,`table`)(9,gk,2,0,`p`,3),z(),A(10,bk,5,1,`div`),z(),R(11,`h4`),K(12,`Wasted checks`),z(),A(13,xk,2,0,`p`,3)(14,Ck,14,0,`table`)(15,wk,2,0,`p`,3),A(16,Ek,16,0),A(17,Ok,5,0),z()),e&2){let e=t,n=W();D(7),j(e.triggers?n.causes().length?8:9:7),D(3),j(n.hasZone()?10:-1),D(3),j(e.waste?n.wasted().length?14:15:13),D(3),j(n.inputs().length?16:-1),D(),j(n.hints().length?17:-1)}}var Ak=10,jk=class e{page=$(null);diagnosis=X(()=>this.page()?.diagnosis??null);causes=X(()=>{let e=this.diagnosis()?.causes??{};return jO.filter(t=>e[t]).map(t=>({kind:t,label:MO[t],count:e[t]}))});zone=X(()=>(this.diagnosis()?.zone??[]).slice(0,Ak));zoneTicks=X(()=>this.diagnosis()?.causes.zone??0);hasZone=X(()=>this.diagnosis()?.zoneJs??(this.zone().length>0||this.zoneTicks()>0));zoneNote=X(()=>{let e=this.zoneTicks();return e?`${e} ${e===1?`tick`:`ticks`} came from zone.js alone, and no component changed in them.`:`No component changed in a tick only zone.js started.`});wasted=X(()=>(this.diagnosis()?.components??[]).filter(e=>e.unchanged>0).slice(0,Ak));inputs=X(()=>(this.diagnosis()?.inputs??[]).slice(0,Ak));hints=X(()=>BO(this.diagnosis()??void 0).slice(0,Ak));share(e,t){return t?`${Math.round(e/t*100)}%`:`0%`}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-cd-diagnosis`]],inputs:{page:[1,`page`]},decls:1,vars:1,consts:[[`aria-labelledby`,`cd-diagnosis-heading`,1,`diagnosis`],[`id`,`cd-diagnosis-heading`],[1,`grid`],[1,`note`],[`scope`,`col`],[`scope`,`col`,1,`num`],[1,`num`],[1,`mono`],[1,`hints`]],template:function(e,t){if(e&1&&A(0,kk,18,5,`section`,0),e&2){let e;j((e=t.diagnosis())?0:-1,e)}},styles:[`[_nghost-%COMP%] { + display: block; +} + +h3[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + margin: 16px 0 6px; +} + +h4[_ngcontent-%COMP%] { + margin: 12px 0 6px; + color: var(--%NS%text-2); + font-size: 12px; + font-weight: 600; +} + +.grid[_ngcontent-%COMP%] { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 0 16px; +} + +@media (min-width: 880px) { + .grid[_ngcontent-%COMP%] { + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + } +} +table[_ngcontent-%COMP%] { + width: 100%; + border-collapse: collapse; + font-size: 12px; +} + +th[_ngcontent-%COMP%], +td[_ngcontent-%COMP%] { + padding: 4px 6px; + border-bottom: 1px solid var(--%NS%border); + text-align: left; +} + +th[_ngcontent-%COMP%] { + color: var(--%NS%text-2); + font-weight: 500; +} + +td.mono[_ngcontent-%COMP%] { + overflow-wrap: anywhere; +} + +.num[_ngcontent-%COMP%] { + text-align: right; + font-variant-numeric: tabular-nums; +} + +.mono[_ngcontent-%COMP%] { + font-family: var(--%NS%font-mono); +} + +.note[_ngcontent-%COMP%] { + margin: 4px 0 8px; + color: var(--%NS%text-2); + font-size: 12px; + line-height: 1.5; +} + +.hints[_ngcontent-%COMP%] { + margin: 0; + padding-left: 18px; + color: var(--%NS%text); + font-size: 12px; + line-height: 1.5; +} + +.hints[_ngcontent-%COMP%] li[_ngcontent-%COMP%] + li[_ngcontent-%COMP%] { + margin-top: 4px; +}`]})},Mk=(e,t)=>t.cycle.id,Nk=(e,t)=>t.id;function Pk(e,t){if(e&1){let e=B();R(0,`button`,27),U(`click`,function(){return S(e),C(W().showAll())}),K(1,`Show all cycles`),z()}}function Fk(e,t){if(e&1&&fg(0,`div`,28),e&2){let e=t;n_(`left`,e.left,`%`)(`width`,e.width,`%`)}}function Ik(e,t){if(e&1&&(R(0,`div`,29),fg(1,`span`,30),z()),e&2){let e=t.$implicit,n=W();G(`over`,e.over)(`out`,!n.inRange(e.index)&&!!n.range())(`active`,n.focused()&&n.active()===e.index),V(`id`,n.optionId(e.index)),k(`data-index`,e.index)(`aria-selected`,n.inRange(e.index))(`aria-label`,e.label),D(),n_(`height`,e.height,`%`)}}function Lk(e,t){e&1&&(R(0,`span`,33),K(1,`over budget`),z())}function Rk(e,t){if(e&1&&(R(0,`p`),K(1,` after `),R(2,`span`,35),K(3),z()()),e&2){let e=W();D(3),q(e.cycle.trigger)}}function zk(e,t){if(e&1&&(R(0,`p`,34)(1,`span`,36),K(2),z(),R(3,`span`),K(4),z()()),e&2){let e=t.$implicit,n=W(2);D(2),q(e.name),D(2),q(n.fmt(e.ms))}}function Bk(e,t){if(e&1&&(R(0,`div`,31)(1,`p`,32)(2,`strong`),K(3),z(),R(4,`span`),K(5),z(),A(6,Lk,2,0,`span`,33),z(),A(7,Rk,4,1,`p`),R(8,`p`),K(9),z(),M(10,zk,5,2,`p`,34,eg),z()),e&2){let e=t,n=W();n_(`left`,e.left)(`right`,e.right),D(3),q(n.fmt(e.cycle.ms)),D(2),J(`cycle `,e.cycle.id),D(),j(e.over?6:-1),D(),j(e.cycle.trigger?7:-1),D(2),k_(` `,e.cycle.checks,` `,e.cycle.checks===1?`check`:`checks`,`, `,e.cycle.passes,` `,e.cycle.passes===1?`pass`:`passes`,` `),D(),N(e.top)}}function Vk(e,t){if(e&1&&fg(0,`span`),e&2){let e=t.$implicit;G(`over`,e.over)}}function Hk(e,t){if(e&1&&K(0),e&2){let e=t,n=W(2);Y(` `,e.name,` (`,n.fmt(e.ms),`) `)}}function Uk(e,t){if(e&1&&(R(0,`tr`)(1,`td`,37),K(2),z(),R(3,`td`,37),K(4),z(),R(5,`td`),K(6),z(),R(7,`td`,37),K(8),z(),R(9,`td`,37),K(10),z(),R(11,`td`,35),K(12),z(),R(13,`td`,35),A(14,Hk,1,2),z()()),e&2){let e,n=t.$implicit,r=W();D(2),q(n.id),D(2),q(r.fmt(n.ms)),D(2),q(r.isOver(n)?`Over`:`Within`),D(2),q(n.checks),D(2),q(n.passes),D(2),q(n.trigger??``),D(2),j((e=n.components[0])?14:-1,e)}}var Wk=0,Gk=80,Kk=10,qk=class e{cycles=$([]);range=mv(null);slowMs=$(16);gapUntil=Gk;budgetLabel=X(()=>`${this.slowMs()} ms budget`);uid=`cd-chart-${Wk++}`;hintId=`${this.uid}-hint`;active=T(null);anchor=null;dragging=!1;hover=T(null);focused=T(!1);max=X(()=>ak(this.cycles(),this.slowMs()));budgetAt=X(()=>this.slowMs()/this.max()*100);bars=X(()=>{let e=this.max();return this.cycles().map((t,n)=>({cycle:t,index:n,height:t.ms/e*100,over:tk(t,this.slowMs()),label:this.describe(t)}))});firstId=X(()=>this.cycles()[0]?.id??0);lastId=X(()=>this.cycles().at(-1)?.id??0);span=X(()=>{let e=this.range();if(!e)return null;let t=this.cycles(),n=Math.min(e.from,e.to),r=Math.max(e.from,e.to),i=t.findIndex(e=>e.id>=n),a=-1;for(let e=t.length-1;e>=0;e--)if(t[e].id<=r){a=e;break}return i<0||ark(this.cycles(),this.range()));startId=X(()=>this.selected()[0]?.id??``);endId=X(()=>this.selected().at(-1)?.id??``);band=X(()=>{let e=this.span(),t=this.cycles().length;return!e||!t?null:{left:e.start/t*100,width:(e.end-e.start+1)/t*100}});summary=X(()=>{let e=this.selected(),t=this.slowMs(),n=e.filter(e=>tk(e,t)).length;return`${this.span()?e.length===1?`Cycle ${e[0].id}`:`Cycles ${e[0].id} to ${e.at(-1).id} (${e.length})`:`All ${e.length} ${e.length===1?`cycle`:`cycles`}`}. ${n} over the ${t} ms budget.`});tableLabel=X(()=>this.span()?`Picked cycles, ${this.summary()}`:`All recorded cycles`);activeOptionId=X(()=>{let e=this.active();return this.focused()&&e!==null&&e{let e=this.hover()??(this.focused()?this.active():null),t=e===null?void 0:this.cycles()[e];return!t||e===null?null:{cycle:t,over:tk(t,this.slowMs()),top:t.components.slice(0,3),...this.tipSide(e)}});tipSide(e){let t=this.cycles().length;return e+.5=t.start&&e<=t.end}describe(e){let t=[`Cycle ${e.id}`,nk(e.ms)];tk(e,this.slowMs())&&t.push(`over budget`),e.trigger&&t.push(`after ${e.trigger}`),t.push(`${e.checks} ${e.checks===1?`check`:`checks`}`);let n=e.components[0];return n&&t.push(`slowest ${n.name} ${nk(n.ms)}`),t.join(`, `)}select(e,t){let n=this.cycles(),r=n[Math.min(e,t)],i=n[Math.max(e,t)];r&&i&&this.range.set({from:r.id,to:i.id})}showAll(){this.range.set(null),this.anchor=null}setEnd(e,t){if(!this.cycles().length)return;let n=t.target,r=Number.parseInt(n.value,10);if(Number.isNaN(r)){n.value=String(e===`from`?this.startId():this.endId());return}let i=Math.min(Math.max(r,this.firstId()),this.lastId()),a=this.selected(),o=e===`from`?i:a[0]?.id??this.firstId(),s=e===`to`?i:a.at(-1)?.id??this.lastId();this.range.set({from:Math.min(o,s),to:Math.max(o,s)}),n.value=String(e===`from`?Math.min(o,s):Math.max(o,s))}onFocus(){this.focused.set(!0),(this.active()===null||this.active()>=this.cycles().length)&&this.active.set(this.span()?.start??Math.max(this.cycles().length-1,0)),this.anchor??=this.active()}onKey(e){let t=this.cycles().length;if(!t)return;let n=Math.min(this.active()??t-1,t-1),r=null;switch(e.key){case`ArrowRight`:case`ArrowDown`:r=n+1;break;case`ArrowLeft`:case`ArrowUp`:r=n-1;break;case`PageDown`:r=n+Kk;break;case`PageUp`:r=n-Kk;break;case`Home`:r=0;break;case`End`:r=t-1;break;case`Enter`:case` `:e.preventDefault(),this.anchor=n,this.select(n,n);return;case`Escape`:this.range()&&(e.preventDefault(),this.showAll());return;default:return}e.preventDefault(),r=Math.min(Math.max(r,0),t-1),this.active.set(r),this.hover.set(null),e.shiftKey?(this.anchor??=n,this.select(this.anchor,r)):this.anchor=r}indexAt(e){let t=e.target?.closest?.(`[data-index]`);if(t)return Number(t.getAttribute(`data-index`));let n=e.currentTarget?.getBoundingClientRect(),r=this.cycles().length;if(!n?.width||!r)return null;let i=(e.clientX-n.left)/n.width;return Math.min(Math.max(Math.floor(i*r),0),r-1)}startDrag(e){if(e.button!==0)return;let t=this.indexAt(e);t!==null&&(this.dragging=!0,this.anchor=t,this.active.set(t),this.select(t,t))}onPointerMove(e){let t=this.indexAt(e);this.hover.set(t),this.dragging&&t!==null&&this.anchor!==null&&(this.active.set(t),this.select(this.anchor,t))}endDrag(){this.dragging=!1}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-cd-cycle-chart`]],hostBindings:function(e,t){e&1&&H(`pointerup`,function(){return t.endDrag()},Ou)(`pointercancel`,function(){return t.endDrag()},Ou)},inputs:{cycles:[1,`cycles`],range:[1,`range`],slowMs:[1,`slowMs`]},outputs:{range:`rangeChange`},decls:69,vars:25,consts:[[1,`range`],[`type`,`number`,`inputmode`,`numeric`,3,`change`,`min`,`max`,`value`],[`type`,`button`],[1,`summary`],[1,`hint`,3,`id`],[1,`plot`],[`aria-hidden`,`true`,1,`axis`],[1,`tick`,`top`],[1,`tick`,`zero`],[1,`area`],[`aria-hidden`,`true`,1,`band`,3,`left`,`width`],[`aria-hidden`,`true`,1,`budget`],[`role`,`listbox`,`aria-multiselectable`,`true`,`aria-orientation`,`horizontal`,`tabindex`,`0`,1,`bars`,3,`keydown`,`focus`,`blur`,`pointerdown`,`pointermove`,`pointerleave`],[`role`,`option`,1,`col`,3,`id`,`over`,`out`,`active`],[`aria-hidden`,`true`,1,`tip`,3,`left`,`right`],[`aria-hidden`,`true`,1,`markers`],[3,`over`],[`aria-label`,`Chart key`,1,`legend`],[`aria-hidden`,`true`,1,`key`,`ok`],[`aria-hidden`,`true`,1,`key`,`over`],[`aria-hidden`,`true`,1,`key-marker`],[`aria-hidden`,`true`,1,`key`,`line`],[1,`table`],[`tabindex`,`0`,`role`,`region`,1,`table-wrap`],[1,`sr-only`],[`scope`,`col`,1,`num`],[`scope`,`col`],[`type`,`button`,3,`click`],[`aria-hidden`,`true`,1,`band`],[`role`,`option`,1,`col`,3,`id`],[1,`bar`],[`aria-hidden`,`true`,1,`tip`],[1,`tip-head`],[1,`flag`],[1,`tip-row`],[1,`mono`],[1,`mono`,`name`],[1,`num`]],template:function(e,t){if(e&1&&(R(0,`div`,0)(1,`label`)(2,`span`),K(3,`Start`),z(),R(4,`input`,1),U(`change`,function(e){return t.setEnd(`from`,e)}),z()(),R(5,`label`)(6,`span`),K(7,`End`),z(),R(8,`input`,1),U(`change`,function(e){return t.setEnd(`to`,e)}),z()(),A(9,Pk,2,0,`button`,2),R(10,`p`,3),K(11),z()(),R(12,`p`,4),K(13,` Drag across the bars to pick a range of cycles. With the chart focused, the arrow keys move between cycles, Shift with an arrow key extends the range, Enter picks one cycle and Escape shows all cycles again. `),z(),R(14,`div`,5)(15,`div`,6)(16,`span`,7),K(17),z(),R(18,`span`,8),K(19,`0 ms`),z()(),R(20,`div`,9),A(21,Fk,1,4,`div`,10),R(22,`div`,11)(23,`span`),K(24),z()(),R(25,`div`,12),U(`keydown`,function(e){return t.onKey(e)})(`focus`,function(){return t.onFocus()})(`blur`,function(){return t.focused.set(!1)})(`pointerdown`,function(e){return t.startDrag(e)})(`pointermove`,function(e){return t.onPointerMove(e)})(`pointerleave`,function(){return t.hover.set(null)}),M(26,Ik,2,12,`div`,13,Mk),z(),A(28,Bk,12,12,`div`,14),z()(),R(29,`div`,15),M(30,Vk,1,2,`span`,16,Mk),z(),R(32,`ul`,17)(33,`li`),fg(34,`span`,18),K(35,`Within budget`),z(),R(36,`li`),fg(37,`span`,19)(38,`span`,20),K(39,`Over budget `),z(),R(40,`li`),fg(41,`span`,21),K(42),z()(),R(43,`details`,22)(44,`summary`),K(45,`Show the cycles as a table`),z(),R(46,`div`,23)(47,`table`)(48,`caption`,24),K(49),z(),R(50,`thead`)(51,`tr`)(52,`th`,25),K(53,`Cycle`),z(),R(54,`th`,25),K(55,`Duration`),z(),R(56,`th`,26),K(57,`Budget`),z(),R(58,`th`,25),K(59,`Checks`),z(),R(60,`th`,25),K(61,`Passes`),z(),R(62,`th`,26),K(63,`After`),z(),R(64,`th`,26),K(65,`Slowest component`),z()()(),R(66,`tbody`),M(67,Uk,15,7,`tr`,null,Nk),z()()()()),e&2){let e,n;D(4),V(`min`,t.firstId())(`max`,t.lastId())(`value`,t.startId()),D(4),V(`min`,t.firstId())(`max`,t.lastId())(`value`,t.endId()),D(),j(t.range()?9:-1),D(2),q(t.summary()),D(),V(`id`,t.hintId),D(2),G(`flush`,t.bars().length>t.gapUntil),D(3),q(t.fmt(t.max())),D(4),j((e=t.band())?21:-1,e),D(),n_(`bottom`,t.budgetAt(),`%`),D(2),q(t.budgetLabel()),D(),k(`aria-label`,`Change detection cycles, `+t.summary())(`aria-describedby`,t.hintId)(`aria-activedescendant`,t.activeOptionId()),D(),N(t.bars()),D(2),j((n=t.tip())?28:-1,n),D(),G(`flush`,t.bars().length>t.gapUntil),D(),N(t.bars()),D(12),q(t.budgetLabel()),D(4),k(`aria-label`,t.tableLabel()),D(3),J(` `,t.tableLabel(),` `),D(18),N(t.selected())}},styles:[`[_nghost-%COMP%] { + display: block; + min-width: 0; +} + +.range[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px 12px; +} + +label[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; + gap: 6px; + color: var(--%NS%text-2); + font-size: 12px; +} + +input[_ngcontent-%COMP%] { + width: 76px; + height: var(--%NS%control-h); + padding: 0 8px; + background: var(--%NS%bg); + border: 1px solid var(--%NS%control-border); + border-radius: var(--%NS%radius-sm); + color: var(--%NS%text); + font: inherit; + font-size: 13px; +} + +input[_ngcontent-%COMP%]:focus-visible { + outline: none; + border-color: var(--%NS%accent); + box-shadow: 0 0 0 3px var(--%NS%accent-soft); +} + +button[_ngcontent-%COMP%] { + height: var(--%NS%control-h); + padding: 0 12px; + background: var(--%NS%surface-2); + border: 1px solid var(--%NS%control-border); + border-radius: var(--%NS%radius-sm); + color: var(--%NS%text); + cursor: pointer; + font: inherit; + font-size: 13px; +} + +button[_ngcontent-%COMP%]:hover { + background: var(--%NS%surface-3); + border-color: var(--%NS%accent-line); +} + +button[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.summary[_ngcontent-%COMP%], +.hint[_ngcontent-%COMP%] { + margin: 0; + color: var(--%NS%text-2); + font-size: 12px; + line-height: 1.5; +} + +.hint[_ngcontent-%COMP%] { + margin: 6px 0 8px; +} + +.plot[_ngcontent-%COMP%] { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 6px; +} + +.axis[_ngcontent-%COMP%] { + position: relative; + width: 44px; + color: var(--%NS%text-2); + font-size: 11px; + text-align: right; +} + +.tick[_ngcontent-%COMP%] { + position: absolute; + right: 0; +} + +.tick.top[_ngcontent-%COMP%] { + top: 0; +} + +.tick.zero[_ngcontent-%COMP%] { + bottom: -6px; +} + +.area[_ngcontent-%COMP%] { + position: relative; + height: 140px; + border-bottom: 1px solid var(--%NS%border-strong); +} + +.bars[_ngcontent-%COMP%] { + position: absolute; + inset: 0; + display: flex; + align-items: stretch; + column-gap: 2px; + touch-action: none; + cursor: crosshair; + user-select: none; +} + +.flush[_ngcontent-%COMP%] .bars[_ngcontent-%COMP%], +.markers.flush[_ngcontent-%COMP%] { + column-gap: 0; +} + +.bars[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.col[_ngcontent-%COMP%] { + position: relative; + display: flex; + flex: 1 1 0; + align-items: flex-end; + justify-content: center; + min-width: 0; +} + +.bar[_ngcontent-%COMP%] { + width: min(24px, 100%); + min-height: 1px; + background: var(--%NS%accent); + border-radius: 4px 4px 0 0; + transition: height 150ms var(--%NS%ease); +} + +.flush[_ngcontent-%COMP%] .bar[_ngcontent-%COMP%] { + border-radius: 1px 1px 0 0; +} + +.col.over[_ngcontent-%COMP%] .bar[_ngcontent-%COMP%] { + background: repeating-linear-gradient(135deg, var(--%NS%danger) 0 3px, color-mix(in srgb, var(--%NS%danger) 55%, var(--%NS%surface)) 3px 6px); +} + +.col.out[_ngcontent-%COMP%] .bar[_ngcontent-%COMP%] { + opacity: 0.4; +} + +.col[_ngcontent-%COMP%]:hover .bar[_ngcontent-%COMP%], +.col.active[_ngcontent-%COMP%] .bar[_ngcontent-%COMP%] { + outline: 2px solid var(--%NS%text-strong); + outline-offset: 1px; +} + +.band[_ngcontent-%COMP%] { + position: absolute; + top: 0; + bottom: 0; + background: var(--%NS%accent-soft); + border-inline: 2px solid var(--%NS%accent); + pointer-events: none; +} + +.budget[_ngcontent-%COMP%] { + position: absolute; + left: 0; + right: 0; + height: 0; + border-top: 1px solid var(--%NS%text-2); + pointer-events: none; + z-index: 1; +} + +.budget[_ngcontent-%COMP%] span[_ngcontent-%COMP%] { + position: absolute; + right: 0; + bottom: 2px; + padding: 0 4px; + background: var(--%NS%surface); + border-radius: var(--%NS%radius-sm); + color: var(--%NS%text-2); + font-size: 11px; +} + +.markers[_ngcontent-%COMP%] { + display: flex; + column-gap: 2px; + height: 8px; + margin: 3px 0 0 50px; +} + +.markers[_ngcontent-%COMP%] span[_ngcontent-%COMP%] { + display: flex; + flex: 1 1 0; + justify-content: center; + min-width: 0; +} + +.markers[_ngcontent-%COMP%] span.over[_ngcontent-%COMP%]::before { + content: ""; + width: min(10px, 100%); +} + +.markers[_ngcontent-%COMP%] span.over[_ngcontent-%COMP%]::before, +.key-marker[_ngcontent-%COMP%] { + background: var(--%NS%danger); + clip-path: polygon(50% 0, 100% 100%, 0 100%); +} + +.key-marker[_ngcontent-%COMP%] { + display: inline-block; + width: 8px; + height: 8px; + margin-right: 6px; +} + +.tip[_ngcontent-%COMP%] { + position: absolute; + top: 4px; + z-index: 2; + width: min(240px, 100% - 8px); + padding: 8px 10px; + background: var(--%NS%surface-3); + border: 1px solid var(--%NS%border-strong); + border-radius: var(--%NS%radius-sm); + color: var(--%NS%text-2); + font-size: 12px; + pointer-events: none; +} + +.tip[_ngcontent-%COMP%] p[_ngcontent-%COMP%] { + margin: 2px 0; +} + +.tip[_ngcontent-%COMP%] strong[_ngcontent-%COMP%] { + color: var(--%NS%text-strong); + font-size: 13px; +} + +.tip-head[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 4px 8px; +} + +.flag[_ngcontent-%COMP%] { + padding: 0 6px; + border: 1px solid; + border-radius: 999px; + border-color: color-mix(in srgb, var(--%NS%danger) 30%, transparent); + background: color-mix(in srgb, var(--%NS%danger) 12%, transparent); + color: var(--%NS%danger-text); +} + +.tip-row[_ngcontent-%COMP%] { + display: flex; + justify-content: space-between; + gap: 8px; +} + +.tip-row[_ngcontent-%COMP%] .name[_ngcontent-%COMP%] { + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; +} + +.legend[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + gap: 4px 16px; + margin: 8px 0 0; + padding: 0; + list-style: none; + color: var(--%NS%text-2); + font-size: 12px; +} + +.legend[_ngcontent-%COMP%] li[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; +} + +.key[_ngcontent-%COMP%] { + display: inline-block; + width: 10px; + height: 10px; + margin-right: 6px; + border-radius: 2px; +} + +.key.ok[_ngcontent-%COMP%] { + background: var(--%NS%accent); +} + +.key.over[_ngcontent-%COMP%] { + background: repeating-linear-gradient(135deg, var(--%NS%danger) 0 3px, color-mix(in srgb, var(--%NS%danger) 55%, var(--%NS%surface)) 3px 6px); +} + +.key.line[_ngcontent-%COMP%] { + width: 14px; + height: 0; + border-top: 1px solid var(--%NS%text-2); + border-radius: 0; +} + +.table[_ngcontent-%COMP%] { + margin-top: 8px; + font-size: 12px; +} + +summary[_ngcontent-%COMP%] { + width: fit-content; + color: var(--%NS%text); + cursor: pointer; +} + +summary[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.table-wrap[_ngcontent-%COMP%] { + max-height: 320px; + margin-top: 6px; + overflow: auto; +} + +.table-wrap[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: -2px; +} + +table[_ngcontent-%COMP%] { + width: 100%; + min-width: 600px; + border-collapse: collapse; +} + +th[_ngcontent-%COMP%], +td[_ngcontent-%COMP%] { + padding: 4px 6px; + border-bottom: 1px solid var(--%NS%border); + text-align: left; + vertical-align: top; +} + +th[_ngcontent-%COMP%] { + position: sticky; + top: 0; + background: var(--%NS%surface); + color: var(--%NS%text-2); + font-weight: 500; +} + +td.mono[_ngcontent-%COMP%] { + overflow-wrap: anywhere; +} + +.num[_ngcontent-%COMP%] { + text-align: right; + font-variant-numeric: tabular-nums; +} + +.mono[_ngcontent-%COMP%] { + font-family: var(--%NS%font-mono); +} + +@media (prefers-reduced-motion: reduce) { + .bar[_ngcontent-%COMP%] { + transition: none; + } +}`]})},Jk=(e,t)=>t.id,Yk=(e,t)=>t.index,Xk=(e,t)=>t.kind;function Zk(e,t){if(e&1&&(R(0,`p`,0),K(1),z()),e&2){let e=W(2);D(),Y(` Pick `,e.max,` cycles or fewer to see their spans. `,e.count(),` are picked. `)}}function Qk(e,t){if(e&1&&(R(0,`span`),K(1),z()),e&2){let e=t.$implicit;n_(`left`,e.x,`%`)(`width`,e.w,`%`),D(),J(`cycle `,e.id)}}function $k(e,t){if(e&1){let e=B();R(0,`button`,11),U(`focus`,function(){let t=S(e).$implicit;return C(W(3).current.set(t.index))})(`mouseenter`,function(){let t=S(e).$implicit;return C(W(3).hover.set(t.index))})(`mouseleave`,function(){return S(e),C(W(3).hover.set(null))})(`click`,function(){let t=S(e).$implicit;return C(W(3).choose(t))}),R(1,`span`,12),K(2),z()()}if(e&2){let e=t.$implicit,n=W(3);r_(R_(`block kind-`,e.kind)),n_(`left`,e.x,`%`)(`width`,e.w,`%`)(`top`,e.depth*n.row,`px`),G(`narrow`,e.w<4),k(`tabindex`,e.index===n.tabStop()?0:-1)(`data-index`,e.index)(`aria-label`,n.describe(e))(`aria-disabled`,e.component?null:`true`),D(2),q(e.name)}}function eA(e,t){if(e&1&&(R(0,`li`),fg(1,`span`,13),K(2),z()),e&2){let e=t.$implicit;D(),r_(R_(`key kind-`,e.kind)),D(),q(e.label)}}function tA(e,t){if(e&1&&(R(0,`p`,0),K(1),z()),e&2){let e=W(3);D(),Y(` Each cycle keeps its longest spans. `,e.dropped(),` shorter `,e.dropped()===1?`span was`:`spans were`,` left out. `)}}function nA(e,t){if(e&1&&(R(0,`tr`)(1,`td`,17),K(2),z(),R(3,`td`,18),K(4),z(),R(5,`td`),K(6),z(),R(7,`td`,17),K(8),z(),R(9,`td`,17),K(10),z(),R(11,`td`,17),K(12),z(),R(13,`td`,17),K(14),z()()),e&2){let e=t.$implicit,n=W(4);D(2),q(e.cycleId),D(),n_(`padding-left`,6+e.depth*8,`px`),D(),q(e.name),D(2),q(n.kindLabel(e)),D(2),q(e.depth),D(2),q(n.fmt(e.start)),D(2),q(n.fmt(e.ms)),D(2),q(n.fmt(e.selfMs))}}function rA(e,t){if(e&1&&(R(0,`div`,10)(1,`table`)(2,`caption`,14),K(3),z(),R(4,`thead`)(5,`tr`)(6,`th`,15),K(7,`Cycle`),z(),R(8,`th`,16),K(9,`Span`),z(),R(10,`th`,16),K(11,`Kind`),z(),R(12,`th`,15),K(13,`Depth`),z(),R(14,`th`,15),K(15,`Start`),z(),R(16,`th`,15),K(17,`Total`),z(),R(18,`th`,15),K(19,`Self`),z()()(),R(20,`tbody`),M(21,nA,15,9,`tr`,null,Yk),z()()()),e&2){let e=W(),t=W(2);k(`aria-label`,t.tableLabel()),D(3),J(` `,t.tableLabel(),` `),D(18),N(e.blocks)}}function iA(e,t){if(e&1){let e=B();R(0,`div`,1),U(`keydown`,function(t){return S(e),C(W(2).onKey(t))}),R(1,`div`,2),M(2,Qk,2,5,`span`,3,Jk),z(),R(4,`div`,4),M(5,$k,3,16,`button`,5,Yk),z()(),R(7,`p`,6),K(8),z(),R(9,`ul`,7),M(10,eA,3,4,`li`,null,Xk),z(),R(12,`p`,8),K(13,` Arrow keys move along a row, Up goes to the enclosing span and Down to the first nested one. Enter on a component shows it in the tree. `),z(),A(14,tA,2,2,`p`,0),R(15,`details`,9),U(`toggle`,function(t){return S(e),C(W(2).onToggle(t))}),R(16,`summary`),K(17,`Show the spans as a table`),z(),A(18,rA,23,2,`div`,10),z()}if(e&2){let e=t,n=W(2);k(`aria-label`,n.label()),D(2),N(e.cycles),D(2),n_(`height`,e.depth*n.row,`px`),D(),N(e.blocks),D(3),q(n.readout()),D(2),N(n.kinds),D(4),j(n.dropped()?14:-1),D(4),j(n.tableOpen()?18:-1)}}function aA(e,t){if(e&1&&(R(0,`h3`),K(1,`Spans in the picked cycles`),z(),A(2,Zk,2,2,`p`,0)(3,iA,19,6)),e&2){let e,t=W();D(2),j(t.tooMany()?2:(e=t.graph())?3:-1,e)}}var oA=22;function sA(e,t){for(let n=e.length-1;n>=0;n--)if(t(e[n]))return e[n]}var cA=class e{cycles=$([]);names=$([]);scope=$(`the recording`);pick=lv();row=oA;max=30;kinds=[`c`,`h`,`r`,`o`].map(e=>({kind:e,label:sk[e]}));current=T(0);tableOpen=T(!1);hover=T(null);hasSpans=X(()=>this.cycles().some(e=>e.spans?.length));count=X(()=>this.cycles().length);tooMany=X(()=>this.count()>30);graph=X(()=>this.tooMany()?null:lk(this.cycles(),this.names()));tabStop=X(()=>Math.min(this.current(),Math.max((this.graph()?.blocks.length??1)-1,0)));dropped=X(()=>this.graph()?.cycles.reduce((e,t)=>e+t.droppedSpans,0)??0);label=X(()=>`Spans in ${this.scope()} over time, nested by depth. Pick a component to show it in the tree.`);readout=X(()=>{let e=(this.graph()?.blocks??[])[this.hover()??this.tabStop()];return e?this.describe(e):``});tableLabel=X(()=>`Timed spans in ${this.scope()}`);fmt(e){return nk(e)}kindLabel(e){return sk[e.kind]}onToggle(e){this.tableOpen.set(e.target.open)}describe(e){let t=[e.name,sk[e.kind],`${nk(e.ms)} total`];return e.selfMs!==e.ms&&t.push(`${nk(e.selfMs)} self`),t.push(e.start<0?`cycle ${e.cycleId}, ${nk(-e.start)} before it started`:`cycle ${e.cycleId} at ${nk(e.start)}`),t.join(`, `)}choose(e){this.current.set(e.index),e.component&&this.pick.emit(e.component)}onKey(e){let t=this.graph()?.blocks??[];if(!t.length)return;let n=t[this.tabStop()],r;switch(e.key){case`ArrowRight`:r=t.find(e=>e.index>n.index&&e.depth===n.depth);break;case`ArrowLeft`:r=sA(t,e=>e.indexe.indexe.parent===n.index)??t.find(e=>e.index>n.index&&e.depth===n.depth+1);break;case`Home`:r=t[0];break;case`End`:r=t.at(-1);break;default:return}e.preventDefault(),this.hover.set(null),r&&(this.current.set(r.index),e.currentTarget.querySelector(`button[data-index="${r.index}"]`)?.focus())}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-cd-flame-graph`]],inputs:{cycles:[1,`cycles`],names:[1,`names`],scope:[1,`scope`]},outputs:{pick:`pick`},decls:1,vars:1,consts:[[1,`note`],[`role`,`group`,1,`flame`,3,`keydown`],[`aria-hidden`,`true`,1,`cycles`],[3,`left`,`width`],[1,`rows`],[`type`,`button`,3,`class`,`narrow`,`left`,`width`,`top`],[`aria-hidden`,`true`,1,`readout`],[`aria-label`,`Span kinds`,1,`legend`],[1,`hint`],[1,`table`,3,`toggle`],[`tabindex`,`0`,`role`,`region`,1,`table-wrap`],[`type`,`button`,3,`focus`,`mouseenter`,`mouseleave`,`click`],[1,`text`],[`aria-hidden`,`true`],[1,`sr-only`],[`scope`,`col`,1,`num`],[`scope`,`col`],[1,`num`],[1,`mono`]],template:function(e,t){e&1&&A(0,aA,4,1),e&2&&j(t.hasSpans()?0:-1)},styles:[`[_nghost-%COMP%] { + display: block; + min-width: 0; +} + +h3[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + margin: 12px 0 6px; +} + +.flame[_ngcontent-%COMP%] { + position: relative; +} + +.cycles[_ngcontent-%COMP%] { + position: relative; + height: 18px; + color: var(--%NS%text-2); + font-size: 11px; +} + +.cycles[_ngcontent-%COMP%] span[_ngcontent-%COMP%] { + position: absolute; + top: 0; + padding-left: 4px; + border-left: 1px solid var(--%NS%border-strong); + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; +} + +.rows[_ngcontent-%COMP%] { + position: relative; + min-height: 22px; +} + +.block[_ngcontent-%COMP%] { + position: absolute; + height: 20px; + min-width: 2px; + padding: 0 4px; + overflow: hidden; + border: 0; + border-radius: 3px; + color: var(--%NS%text-strong); + font: inherit; + font-family: var(--%NS%font-mono); + font-size: 11px; + line-height: 20px; + text-align: left; + cursor: default; +} + +.block.narrow[_ngcontent-%COMP%] { + padding: 0; +} + +.block.narrow[_ngcontent-%COMP%] .text[_ngcontent-%COMP%] { + display: none; +} + +.text[_ngcontent-%COMP%] { + display: block; + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; +} + + +.kind-c[_ngcontent-%COMP%] { + background: color-mix(in srgb, var(--%NS%accent) 35%, var(--%NS%surface)); +} + +.block[_ngcontent-%COMP%]:not([aria-disabled]) { + cursor: pointer; +} + +.kind-h[_ngcontent-%COMP%] { + background: repeating-linear-gradient(45deg, color-mix(in srgb, var(--%NS%accent) 30%, var(--%NS%surface)) 0 4px, color-mix(in srgb, var(--%NS%accent) 10%, var(--%NS%surface)) 4px 8px); +} + +.kind-r[_ngcontent-%COMP%] { + background-color: var(--%NS%surface-3); + background-image: radial-gradient(circle, var(--%NS%text-3) 1px, transparent 1.6px); + background-size: 6px 6px; +} + +.kind-o[_ngcontent-%COMP%] { + background-color: var(--%NS%surface-2); + background-image: repeating-linear-gradient(0deg, var(--%NS%border-strong) 0 1px, transparent 1px 5px); + box-shadow: inset 0 0 0 1px var(--%NS%text-2); +} + +.block[_ngcontent-%COMP%]:hover { + outline: 2px solid var(--%NS%text-strong); + outline-offset: -2px; +} + +.block[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: -2px; + outline-color: var(--%NS%text-strong); + z-index: 1; +} + +.readout[_ngcontent-%COMP%], +.hint[_ngcontent-%COMP%], +.note[_ngcontent-%COMP%] { + margin: 6px 0 0; + color: var(--%NS%text-2); + font-size: 12px; + line-height: 1.5; +} + +.readout[_ngcontent-%COMP%] { + min-height: 18px; + color: var(--%NS%text); + overflow-wrap: anywhere; +} + +.legend[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + gap: 4px 16px; + margin: 6px 0 0; + padding: 0; + list-style: none; + color: var(--%NS%text-2); + font-size: 12px; +} + +.legend[_ngcontent-%COMP%] li[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; +} + +.key[_ngcontent-%COMP%] { + display: inline-block; + width: 18px; + height: 12px; + margin-right: 6px; + border-radius: 2px; +} + +.table[_ngcontent-%COMP%] { + margin-top: 6px; + font-size: 12px; +} + +summary[_ngcontent-%COMP%] { + width: fit-content; + color: var(--%NS%text); + cursor: pointer; +} + +summary[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.table-wrap[_ngcontent-%COMP%] { + max-height: 320px; + margin-top: 6px; + overflow: auto; +} + +.table-wrap[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: -2px; +} + +table[_ngcontent-%COMP%] { + width: 100%; + min-width: 560px; + border-collapse: collapse; +} + +th[_ngcontent-%COMP%], +td[_ngcontent-%COMP%] { + padding: 4px 6px; + border-bottom: 1px solid var(--%NS%border); + text-align: left; + vertical-align: top; +} + +th[_ngcontent-%COMP%] { + position: sticky; + top: 0; + background: var(--%NS%surface); + color: var(--%NS%text-2); + font-weight: 500; +} + +td.mono[_ngcontent-%COMP%] { + font-family: var(--%NS%font-mono); + overflow-wrap: anywhere; +} + +.num[_ngcontent-%COMP%] { + text-align: right; + font-variant-numeric: tabular-nums; +}`]})};function lA(e,t){if(e&1&&(R(0,`tr`)(1,`td`,5),K(2),z(),R(3,`td`,6),K(4),z(),R(5,`td`,6),K(6),z(),R(7,`td`,6),K(8),z()()),e&2){let e=t.$implicit,n=W(3);D(2),Y(``,e.name,`.`,e.hook),D(2),q(e.calls),D(2),q(n.ms(e.ms)),D(2),q(n.ms(e.maxMs))}}function uA(e,t){if(e&1&&(R(0,`div`)(1,`h3`),K(2,`Lifecycle hooks`),z(),R(3,`table`)(4,`thead`)(5,`tr`)(6,`th`,3),K(7,`Hook`),z(),R(8,`th`,4),K(9,`Calls`),z(),R(10,`th`,4),K(11,`Time`),z(),R(12,`th`,4),K(13,`Slowest`),z()()(),R(14,`tbody`),M(15,lA,9,5,`tr`,null,eg),z()()()),e&2){let e=W(2);D(15),N(e.hooks())}}function dA(e,t){if(e&1&&K(0),e&2){let e=W().$implicit;J(` (`,e.listener,`) `)}}function fA(e,t){if(e&1&&(R(0,`tr`)(1,`td`,5),K(2),A(3,dA,1,1),z(),R(4,`td`,6),K(5),z(),R(6,`td`,6),K(7),z(),R(8,`td`,6),K(9),z()()),e&2){let e=t.$implicit,n=W(3);D(2),J(` `,e.name,` `),D(),j(e.listener?3:-1),D(2),q(e.calls),D(2),q(n.ms(e.ms)),D(2),q(n.ms(e.maxMs))}}function pA(e,t){if(e&1&&(R(0,`div`)(1,`h3`),K(2,`Output listeners`),z(),R(3,`table`)(4,`thead`)(5,`tr`)(6,`th`,3),K(7,`Listener`),z(),R(8,`th`,4),K(9,`Calls`),z(),R(10,`th`,4),K(11,`Time`),z(),R(12,`th`,4),K(13,`Slowest`),z()()(),R(14,`tbody`),M(15,fA,10,5,`tr`,null,eg),z()()()),e&2){let e=W(2);D(15),N(e.outputs())}}function mA(e,t){if(e&1&&(R(0,`p`,1),K(1),z()),e&2){let e=t,n=W(2);D(),k_(` afterRender callbacks ran `,e.runs,` `,e.runs===1?`time`:`times`,`, `,n.ms(e.ms),` in total, slowest `,n.ms(e.maxMs),`. `)}}function hA(e,t){if(e&1&&(R(0,`p`,2),K(1),z()),e&2){let e=W(2);D(),Y(` `,e.dropped(),` more hook or output `,e.dropped()===1?`row is`:`rows are`,` left out. A recording lists at most 100 of each. `)}}function gA(e,t){if(e&1&&(R(0,`div`,0),A(1,uA,17,0,`div`),A(2,pA,17,0,`div`),z(),A(3,mA,2,4,`p`,1),A(4,hA,2,2,`p`,2)),e&2){let e,t=W();D(),j(t.hooks().length?1:-1),D(),j(t.outputs().length?2:-1),D(),j((e=t.afterRender())?3:-1,e),D(),j(t.dropped()?4:-1)}}var _A=10;function vA(e){return`${e<10?e.toFixed(2):Math.round(e)} ms`}var yA=class e{page=$(null);hooks=X(()=>(this.page()?.hooks??[]).slice(0,_A));outputs=X(()=>(this.page()?.outputs??[]).slice(0,_A));afterRender=X(()=>{let e=this.page()?.afterRender;return e?.runs?e:null});dropped=X(()=>this.page()?.droppedTimings??0);ms(e){return vA(e)}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-cd-timing`]],inputs:{page:[1,`page`]},decls:1,vars:1,consts:[[1,`timing`],[1,`after-render`],[1,`note`],[`scope`,`col`],[`scope`,`col`,1,`num`],[1,`mono`],[1,`num`]],template:function(e,t){e&1&&A(0,gA,5,4),e&2&&j(t.hooks().length||t.outputs().length||t.afterRender()?0:-1)},styles:[`[_nghost-%COMP%] { + display: block; +} + +.timing[_ngcontent-%COMP%] { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 0 16px; +} + +@media (min-width: 880px) { + .timing[_ngcontent-%COMP%] { + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + } +} +h3[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + margin: 12px 0 6px; +} + +table[_ngcontent-%COMP%] { + width: 100%; + border-collapse: collapse; + font-size: 12px; +} + +th[_ngcontent-%COMP%], +td[_ngcontent-%COMP%] { + padding: 4px 6px; + border-bottom: 1px solid var(--%NS%border); + text-align: left; +} + +th[_ngcontent-%COMP%] { + color: var(--%NS%text-2); + font-weight: 500; +} + +td.mono[_ngcontent-%COMP%] { + overflow-wrap: anywhere; +} + +.num[_ngcontent-%COMP%] { + text-align: right; + font-variant-numeric: tabular-nums; +} + +.mono[_ngcontent-%COMP%] { + font-family: var(--%NS%font-mono); +} + +.after-render[_ngcontent-%COMP%], +.note[_ngcontent-%COMP%] { + margin: 8px 0 0; + color: var(--%NS%text-2); + font-size: 12px; +}`]})};function bA(e,t){if(e&1&&(R(0,`p`,5),K(1),z()),e&2){let e=W();D(),q(e.error())}}function xA(e,t){let n=new Blob([JSON.stringify(e,null,2)],{type:`application/json`}),r=URL.createObjectURL(n),i=document.createElement(`a`);i.href=r,i.download=t,document.body.append(i),i.click(),i.remove(),setTimeout(()=>URL.revokeObjectURL(r),0)}var SA=class e{page=$(null);maxCycles=$(200);imported=lv();error=T(``);hasCycles(){return(this.page()?.cycles.length??0)>0}exportJson(){let e=this.page();e&&xA(qO(e,e.pageId),`change-detection-${e.pageId}.json`)}exportTrace(){let e=this.page();e&&xA(ek(e,e.pageId),`change-detection-${e.pageId}.trace.json`)}async read(e){let t=e.files?.[0];if(e.value=``,!t)return;if(t.size>2e7){this.error.set(`${t.name}: The file is larger than 20 MB.`);return}let n;try{n=await t.text()}catch{this.error.set(`Could not read the file.`);return}let r=JO(n,this.maxCycles());if(!r.ok){this.error.set(`${t.name}: ${r.error}`);return}this.error.set(``),this.imported.emit({fileName:t.name,page:{...r.recording,pageId:r.pageId,supported:!0}})}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-cd-transfer`]],inputs:{page:[1,`page`],maxCycles:[1,`maxCycles`]},outputs:{imported:`imported`},decls:10,vars:3,consts:[[`file`,``],[`role`,`group`,`aria-label`,`Export and import the recording`,1,`transfer`],[`type`,`button`,3,`click`,`disabled`],[`type`,`button`,3,`click`],[`type`,`file`,`accept`,`.json,application/json`,`hidden`,``,3,`change`],[`role`,`alert`,1,`error`]],template:function(e,t){if(e&1){let e=B();R(0,`div`,1)(1,`button`,2),U(`click`,function(){return t.exportJson()}),K(2,`Export JSON`),z(),R(3,`button`,2),U(`click`,function(){return t.exportTrace()}),K(4,` Export trace (Perfetto) `),z(),R(5,`button`,3),U(`click`,function(){return S(e),C(Fg(8).click())}),K(6,`Import`),z(),R(7,`input`,4,0),U(`change`,function(){S(e);let n=Fg(8);return C(t.read(n))}),z()(),A(9,bA,2,1,`p`,5)}e&2&&(D(),V(`disabled`,!t.hasCycles()),D(2),V(`disabled`,!t.hasCycles()),D(6),j(t.error()?9:-1))},styles:[`[_nghost-%COMP%] { + display: contents; +} + +.transfer[_ngcontent-%COMP%] { + display: inline-flex; + flex-wrap: wrap; + gap: 8px; +} + +button[_ngcontent-%COMP%] { + height: var(--%NS%control-h); + padding: 0 12px; + background: var(--%NS%surface-2); + border: 1px solid var(--%NS%control-border); + border-radius: var(--%NS%radius-sm); + color: var(--%NS%text); + cursor: pointer; + font: inherit; + font-size: 13px; +} + +button[_ngcontent-%COMP%]:hover:not(:disabled) { + background: var(--%NS%surface-3); + border-color: var(--%NS%accent-line); +} + +button[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +button[_ngcontent-%COMP%]:disabled { + cursor: not-allowed; + opacity: 0.6; +} + +.error[_ngcontent-%COMP%] { + flex-basis: 100%; + margin: 0; + color: var(--%NS%danger-text); + font-size: 12px; +}`]})},CA=(e,t)=>t.stat.name;function wA(e,t){if(e&1&&(R(0,`span`,8),K(1),z()),e&2){let e=W(2).$implicit;D(),J(``,e.other,` more`)}}function TA(e,t){if(e&1&&(R(0,`div`,6)(1,`span`,7),K(2),z(),A(3,wA,2,1,`span`,8),z()),e&2){let e=W().$implicit;n_(`--%NS%tone`,e.tone+`%`),D(2),q(e.label),D(),j(e.roomy?3:-1)}}function EA(e,t){if(e&1&&(R(0,`span`,10),K(1),z(),R(2,`span`,11),K(3),z()),e&2){let e=W(2).$implicit,t=W(2);D(),q(e.stat.name),D(2),q(t.fmt(e.stat.ms))}}function DA(e,t){if(e&1){let e=B();R(0,`button`,9),U(`focus`,function(){S(e);let t=W().$index;return C(W(2).current.set(t))})(`click`,function(){S(e);let t=W().$implicit;return C(W(2).pick.emit(t.stat.name))}),A(1,EA,4,2),z()}if(e&2){let e=W(),t=e.$implicit,n=e.$index,r=W(2);n_(`--%NS%tone`,t.tone+`%`),k(`tabindex`,n===r.tabStop()?0:-1)(`aria-label`,t.label)(`data-index`,n),D(),j(t.roomy?1:-1)}}function OA(e,t){if(e&1&&(R(0,`li`),A(1,TA,4,4,`div`,4)(2,DA,2,6,`button`,5),z()),e&2){let e=t.$implicit;n_(`left`,e.x,`%`)(`top`,e.y,`%`)(`width`,e.w,`%`)(`height`,e.h,`%`),D(),j(e.other?1:2)}}function kA(e,t){if(e&1){let e=B();R(0,`ul`,2),U(`keydown`,function(t){return S(e),C(W().onKey(t))}),M(1,OA,3,9,`li`,3,CA),z()}if(e&2){let e=W();k(`aria-label`,e.label()),D(),N(e.cells())}}function AA(e,t){e&1&&(R(0,`p`,1),K(1,`No component time in these cycles.`),z())}var jA=24,MA=3,NA=class e{stats=$([]);scope=$(`the recording`);pick=lv();current=T(0);label=X(()=>`Self time per component in ${this.scope()}, largest first. Pick one to show it in the tree.`);cells=X(()=>{let e=this.stats().filter(e=>e.ms>0),t=e.slice(0,jA),n=e.slice(jA),r=n.reduce((e,t)=>e+t.ms,0),i=t.map(e=>({stat:e,other:0}));n.length&&i.push({stat:{name:`Other components`,checks:0,ms:r,maxMs:0,cycles:0},other:n.length});let a=e.reduce((e,t)=>e+t.ms,0),o=t[0]?.ms??1,s=ok(i,e=>e.stat.ms,MA);return s.sort((e,t)=>Number(e.item.other>0)-Number(t.item.other>0)),s.map(e=>{let{stat:t,other:n}=e.item,r=a?Math.round(t.ms/a*100):0;return{stat:t,other:n,x:e.x,y:e.y,w:e.w,h:e.h,tone:n?0:14+Math.round(26*Math.min(t.ms/o,1)),roomy:e.w>=10&&e.h>=22,label:n?`${n} more components, ${nk(t.ms)}, ${r}% of self time`:`${t.name}, ${nk(t.ms)} self time, ${r}%, ${t.checks} ${t.checks===1?`check`:`checks`}`}})});tabStop=X(()=>Math.min(this.current(),Math.max(this.cells().filter(e=>!e.other).length-1,0)));fmt(e){return nk(e)}onKey(e){let t=this.cells().filter(e=>!e.other).length;if(!t)return;let n=this.tabStop(),r;switch(e.key){case`ArrowRight`:case`ArrowDown`:r=Math.min(n+1,t-1);break;case`ArrowLeft`:case`ArrowUp`:r=Math.max(n-1,0);break;case`Home`:r=0;break;case`End`:r=t-1;break;default:return}e.preventDefault(),this.current.set(r),e.currentTarget.querySelector(`button[data-index="${r}"]`)?.focus()}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-cd-treemap`]],inputs:{stats:[1,`stats`],scope:[1,`scope`]},outputs:{pick:`pick`},decls:2,vars:1,consts:[[1,`map`],[1,`empty`],[1,`map`,3,`keydown`],[3,`left`,`top`,`width`,`height`],[1,`cell`,`other`,3,`--%NS%tone`],[`type`,`button`,1,`cell`,3,`--%NS%tone`],[1,`cell`,`other`],[1,`sr-only`],[`aria-hidden`,`true`,1,`name`],[`type`,`button`,1,`cell`,3,`focus`,`click`],[1,`name`,`mono`],[1,`ms`]],template:function(e,t){e&1&&A(0,kA,3,1,`ul`,0)(1,AA,2,0,`p`,1),e&2&&j(+!t.cells().length)},styles:[`[_nghost-%COMP%] { + display: block; + min-width: 0; +} + +.map[_ngcontent-%COMP%] { + position: relative; + height: 180px; + margin: 0; + padding: 0; + list-style: none; +} + +li[_ngcontent-%COMP%] { + position: absolute; + padding: 1px; +} + +.cell[_ngcontent-%COMP%] { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + gap: 2px; + width: 100%; + height: 100%; + padding: 4px 6px; + overflow: hidden; + background: color-mix(in srgb, var(--%NS%accent) var(--%NS%tone, 20%), var(--%NS%surface)); + border: 0; + border-radius: var(--%NS%radius-sm); + color: var(--%NS%text-strong); + font: inherit; + font-size: 12px; + text-align: left; +} + +button.cell[_ngcontent-%COMP%] { + cursor: pointer; +} + +button.cell[_ngcontent-%COMP%]:hover { + outline: 2px solid var(--%NS%text-strong); + outline-offset: -2px; +} + +button.cell[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: -2px; + outline-color: var(--%NS%text-strong); +} + +.cell.other[_ngcontent-%COMP%] { + background: var(--%NS%surface-3); + color: var(--%NS%text-2); +} + +.name[_ngcontent-%COMP%] { + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 100%; +} + +.mono[_ngcontent-%COMP%] { + font-family: var(--%NS%font-mono); +} + +.ms[_ngcontent-%COMP%] { + color: var(--%NS%text); +} + +.empty[_ngcontent-%COMP%] { + margin: 8px 0; + color: var(--%NS%text-2); +}`]})},PA=(e,t)=>t.id;function FA(e,t){if(e&1){let e=B();F(0,`button`,14),H(`click`,function(){return S(e),C(W(2).clear())}),K(1,`Clear`),I()}}function IA(e,t){if(e&1){let e=B();F(0,`button`,11),H(`click`,function(){return S(e),C(W().toggle())}),L(1,`span`,12),K(2),I(),A(3,FA,2,0,`button`,13)}if(e&2){let e=W();G(`on`,e.recording()),P(`disabled`,!e.pageId()),D(2),J(` `,e.recording()?`Stop recording`:`Record`,` `),D(),j(e.cycles().length?3:-1)}}function LA(e,t){if(e&1){let e=B();F(0,`p`,6)(1,`span`,15),K(2,`Imported recording`),I(),F(3,`span`,16),K(4),I(),F(5,`button`,14),H(`click`,function(){return S(e),C(W().closeImport())}),K(6,`Back to live recording`),I()()}e&2&&(D(4),q(t.fileName))}function RA(e,t){e&1&&K(0,` An app on a device has no DOM, so checks that changed nothing are not counted there. `)}function zA(e,t){e&1&&K(0,` A recording keeps running when the tab reloads, so it catches the next start of the app. The cycles from before the reload are gone with the old page. `)}function BA(e,t){e&1&&(F(0,`p`,9),K(1,` The page has Angular's profiler hook, but the recording could not attach to it yet. The page tries again on its next update while recording is on. If this stays, stop and start the recording, or reload the page. `),I())}function VA(e,t){e&1&&L(0,`app-angular-unavailable`,17),e&2&&P(`page`,W(2).angularPage())}function HA(e,t){e&1&&A(0,BA,2,0,`p`,9)(1,VA,1,1,`app-angular-unavailable`,17),e&2&&j(+!W().attachPending())}function UA(e,t){e&1&&(F(0,`p`,9),K(1,`Angular profiling stays on until the page reloads.`),I())}function WA(e,t){e&1&&L(0,`app-angular-unavailable`,21),e&2&&P(`page`,W(2).angularPage())}function GA(e,t){if(e&1){let e=B();F(0,`div`,18)(1,`span`,19),K(2,`Live views`),I(),F(3,`button`,20),H(`click`,function(){S(e);let t=W();return C(t.setView(`flash`,!t.views().flash))}),L(4,`span`,12),K(5,` Flash checks `),I(),F(6,`button`,20),H(`click`,function(){S(e);let t=W();return C(t.setView(`track`,!t.views().track))}),L(7,`span`,12),K(8,` Performance track `),I(),F(9,`button`,20),H(`click`,function(){return S(e),C(W().setView(`angularProfiling`,!0))}),L(10,`span`,12),K(11,` Angular profiling `),I()(),F(12,`p`,7),K(13,` Flash checks draws a box over each component on the page as Angular checks it, with its name and how often it was checked, fading over a second. Performance track adds a Pangular track to the Chrome Performance panel with every component check, hook and output. Angular profiling turns on Angular's own track there. `),I(),A(14,UA,2,0,`p`,9)(15,WA,1,1,`app-angular-unavailable`,21)}if(e&2){let e=W();D(3),P(`disabled`,!e.canShowViews()),k(`aria-pressed`,e.views().flash),D(3),P(`disabled`,!e.canShowViews()),k(`aria-pressed`,e.views().track),D(3),P(`disabled`,!e.canShowViews()||e.views().angularProfiling||!e.views().angularProfilingAvailable),k(`aria-pressed`,e.views().angularProfiling),D(5),j(e.views().angularProfiling?14:e.views().angularProfilingAvailable?-1:15)}}function KA(e,t){if(e&1&&(F(0,`p`,9),K(1),I()),e&2){let e=t.$implicit;D(),q(e)}}function qA(e,t){if(e&1&&(F(0,`p`,24),K(1),I()),e&2){let e=W(2);D(),J(` `,e.range()?`The picked cycles have no timed spans, so there is no flame graph for them.`:`This recording has no timed spans, so there is no flame graph. A recording from an older version, or a file saved without spans, has none.`,` `)}}function JA(e,t){e&1&&(F(0,`p`,9),K(1,` For picked cycles, totals come from the 10 slowest components each cycle lists. `),I())}function YA(e,t){if(e&1){let e=B();F(0,`h3`),K(1,`Cycles`),I(),F(2,`app-cd-cycle-chart`,22),N_(`rangeChange`,function(t){S(e);let n=W();return M_(n.range,t)||(n.range=t),C(t)}),I(),F(3,`app-cd-flame-graph`,23),H(`pick`,function(t){return S(e),C(W().showComponent.emit(t))}),I(),A(4,qA,2,1,`p`,24),F(5,`h3`),K(6,`Self time by component`),I(),A(7,JA,2,0,`p`,9),F(8,`app-cd-treemap`,25),H(`pick`,function(t){return S(e),C(W().showComponent.emit(t))}),I()}if(e&2){let e=W();D(2),P(`cycles`,e.cycles())(`slowMs`,e.slowMs()),j_(`range`,e.range),D(),P(`cycles`,e.picked())(`names`,e.spanNames())(`scope`,e.scope()),D(),j(e.hasSpans()?-1:4),D(3),j(e.range()?7:-1),D(),P(`stats`,e.stats())(`scope`,e.scope())}}function XA(e,t){if(e&1){let e=B();F(0,`tr`)(1,`td`,16)(2,`button`,31),H(`click`,function(){let t=S(e).$implicit;return C(W(2).showComponent.emit(t.name))}),K(3),I()(),F(4,`td`,32),K(5),I(),F(6,`td`,32),K(7),I(),F(8,`td`,32),K(9),I()()}if(e&2){let e=t.$implicit,n=W(2);D(3),J(` `,e.name,` `),D(2),q(e.checks),D(2),q(n.ms(e.ms)),D(2),q(n.ms(e.maxMs))}}function ZA(e,t){e&1&&(F(0,`tr`)(1,`td`,33),K(2,`No component time in the picked cycles.`),I()())}function QA(e,t){if(e&1){let e=B();F(0,`tr`)(1,`td`,16)(2,`button`,31),H(`click`,function(){let t=S(e).$implicit;return C(W(2).showComponent.emit(t.name))}),K(3),I()(),F(4,`td`,32),K(5),I(),F(6,`td`,32),K(7),I(),F(8,`td`,32),K(9),I()()}if(e&2){let e=t.$implicit,n=W(2);D(3),J(` `,e.name,` `),D(2),q(e.checks),D(2),q(e.cycles),D(2),q(n.ms(e.ms))}}function $A(e,t){e&1&&(F(0,`tr`)(1,`td`,33),K(2,`No component checks in the picked cycles.`),I()())}function ej(e,t){if(e&1&&(F(0,`span`,35),K(1,`from `),F(2,`span`,16),K(3),I()()),e&2){let e=W(3);D(3),q(e.causeText(t))}}function tj(e,t){if(e&1&&(F(0,`span`,35),K(1,`after `),F(2,`span`,16),K(3),I()()),e&2){let e=W().$implicit;D(3),q(e.trigger)}}function nj(e,t){if(e&1&&(F(0,`span`,36),K(1),I()),e&2){let e=W().$implicit;D(),J(``,e.unchanged,` changed nothing`)}}function rj(e,t){if(e&1&&(F(0,`span`,36),K(1),I()),e&2){let e=W().$implicit,t=W(2);D(),J(`hooks `,t.ms(e.hookMs))}}function ij(e,t){if(e&1&&(F(0,`span`,36),K(1),I()),e&2){let e=W().$implicit,t=W(2);D(),J(`afterRender `,t.ms(e.afterRenderMs))}}function aj(e,t){if(e&1&&(F(0,`span`,37),K(1,` slowest `),F(2,`span`,16),K(3),I(),K(4),I()),e&2){let e=t,n=W(3);D(3),q(e.name),D(),J(` `,n.ms(e.ms),` `)}}function oj(e,t){if(e&1&&(F(0,`li`)(1,`span`,34),K(2),I(),F(3,`span`,16),K(4),I(),F(5,`span`),K(6),I(),A(7,ej,4,1,`span`,35)(8,tj,4,1,`span`,35),A(9,nj,2,1,`span`,36),A(10,rj,2,1,`span`,36),A(11,ij,2,1,`span`,36),A(12,aj,5,2,`span`,37),I()),e&2){let e,n,r=t.$implicit,i=W(2);D(2),q(i.clock(r.at)),D(2),q(i.ms(r.ms)),D(2),k_(` `,r.checks,` `,r.checks===1?`check`:`checks`,`, `,r.passes,` `,r.passes===1?`pass`:`passes`,` `),D(),j((e=r.cause)?7:r.trigger?8:-1,e),D(2),j(r.unchanged?9:-1),D(),j(r.hookMs?10:-1),D(),j(r.afterRenderMs?11:-1),D(),j((n=r.components[0])?12:-1,n)}}function sj(e,t){if(e&1&&(F(0,`div`,26)(1,`div`)(2,`h3`),K(3),I(),F(4,`table`)(5,`thead`)(6,`tr`)(7,`th`,27),K(8,`Component`),I(),F(9,`th`,28),K(10,`Checks`),I(),F(11,`th`,28),K(12,`Self time`),I(),F(13,`th`,28),K(14,`Slowest`),I()()(),F(15,`tbody`),M(16,XA,10,4,`tr`,null,eg,!1,ZA,3,0,`tr`),I()(),F(19,`h3`),K(20),I(),F(21,`table`)(22,`thead`)(23,`tr`)(24,`th`,27),K(25,`Component`),I(),F(26,`th`,28),K(27,`Checks`),I(),F(28,`th`,28),K(29,`Cycles`),I(),F(30,`th`,28),K(31,`Self time`),I()()(),F(32,`tbody`),M(33,QA,10,4,`tr`,null,eg,!1,$A,3,0,`tr`),I()(),F(36,`p`,7),K(37,` Select a component name to show its most checked instance in the tree. `),I()(),F(38,`div`)(39,`h3`),K(40,`Latest cycles`),I(),F(41,`ol`,29),M(42,oj,13,11,`li`,null,PA),I()()(),L(44,`app-cd-timing`,30)(45,`app-cd-diagnosis`,30)),e&2){let e=W();D(3),J(` `,e.range()?`Slowest components in the picked cycles`:`Slowest components`,` `),D(13),N(e.slowest()),D(4),J(` `,e.range()?`Most often checked in the picked cycles`:`Most often checked`,` `),D(13),N(e.busiest()),D(9),N(e.latest()),D(2),P(`page`,e.page()),D(),P(`page`,e.page())}}function cj(e,t){e&1&&(F(0,`p`,10),K(1,`Use the app. Cycles show up here as Angular runs change detection.`),I())}var lj=3e3,uj=10,dj=class e{livePage=$(null,{alias:`page`});pageId=$(null);rpc=$(null);native=$(!1);angularPages=hD(()=>this.rpc());angularPage=X(()=>gD(this.angularPages(),this.pageId()));attachPending=X(()=>this.angularPage()?.capabilities.profiler===!0);showComponent=lv();range=T(null);imported=T(null);page=X(()=>this.imported()?.page??this.livePage());views=Q(()=>this.livePage()?.options??{flash:!1,track:!1,angularProfiling:!1,angularProfilingAvailable:!0});canShowViews=X(()=>!!this.pageId()&&this.livePage()?.supported!==!1);message=T(``);announcement=T(``);recording=X(()=>this.page()?.recording===!0);cycles=X(()=>this.page()?.cycles??[]);slowMs=X(()=>this.page()?.slowMs??16);maxCycles=X(()=>PT(this.rpc()).limits.cdCycles);picked=X(()=>rk(this.cycles(),this.range()));hasSpans=X(()=>this.picked().some(e=>e.spans?.length));spanNames=X(()=>this.page()?.spanNames??[]);stats=X(()=>this.range()?ik(this.picked()):[...this.page()?.components??[]].sort((e,t)=>t.ms-e.ms));scope=X(()=>{let e=this.range()?this.picked():[];return e.length?e.length===1?`cycle ${e[0].id}`:`cycles ${e[0].id} to ${e.at(-1).id}`:`the recording`});slowest=X(()=>this.stats().slice(0,uj));busiest=X(()=>[...this.stats()].sort((e,t)=>t.checks-e.checks).slice(0,uj));latest=X(()=>this.cycles().slice(-30).reverse());capNotes=X(()=>{let e=this.page()?.droppedClasses??0,t=this.page()?.droppedHosts??0,n=[];return e>0&&n.push(`${e} more component ${e===1?`class was`:`classes were`} checked than a recording keeps, so ${e===1?`it is`:`they are`} left out of these totals.`),t>0&&n.push(`${t} component ${t===1?`instance was`:`instances were`} checked past the 2000 a recording counts, so ${t===1?`its tree row shows`:`their tree rows show`} no check count.`),n});status=X(()=>{if(this.message())return this.message();let e=this.cycles().length;return this.imported()?`${e} ${e===1?`cycle`:`cycles`} imported.`:this.recording()?`Recording. ${e} ${e===1?`cycle`:`cycles`} so far.`:e?`${e} ${e===1?`cycle`:`cycles`} recorded.`:``});ms(e){return`${e<10?e.toFixed(2):Math.round(e)} ms`}clock(e){return XE(e)}causeText(e){return zO(e)}confirming=T(null);confirmTimer;constructor(){E(()=>{let e=this.confirming();e!==null&&this.livePage()?.recording===!0===e&&Z(()=>this.confirmed(e))}),x(Ss).onDestroy(()=>clearTimeout(this.confirmTimer))}confirmed(e){clearTimeout(this.confirmTimer),this.confirming.set(null),this.message.set(``);let t=this.livePage()?.cycles.length??0;this.announce(e?`Recording started.`:`Recording stopped. ${t} ${t===1?`cycle`:`cycles`} recorded.`)}unconfirmed(e){let t=`The page did not confirm that the recording ${e?`started`:`stopped`}. The tab may be in the background, closed or reloading.`;this.message.set(t),this.announce(t)}async toggle(){let e=!this.recording();clearTimeout(this.confirmTimer),this.confirming.set(null),await this.send({on:e},e?`Starting…`:`Stopping…`,()=>``,`request-change-detection-record`,{quiet:!0,keepPending:!0})&&(this.confirming.set(e),this.confirmTimer=setTimeout(()=>{this.confirming()===e&&this.unconfirmed(e)},lj))}showImport(e){this.message.set(``),this.range.set(null),this.imported.set(e);let t=e.page.cycles.length;this.announce(`Imported ${e.fileName}: ${t} ${t===1?`cycle`:`cycles`}. Showing the imported recording.`)}closeImport(){this.imported.set(null),this.range.set(null),this.announce(`Showing the live recording.`)}async clear(){this.range.set(null),await this.send({clear:!0},``,()=>`Cleared the recorded cycles.`)}async setView(e,t){let n=this.views();this.views.set({...n,[e]:t});let r={flash:t?`Flash checks on.`:`Flash checks off.`,track:t?`Performance track on. Record in the Chrome Performance panel to see it.`:`Performance track off.`,angularProfiling:`Angular profiling on. It stays on until the page reloads.`}[e];await this.send({[e]:t},``,()=>r,`request-change-detection-options`)||this.views.set(n)}async send(e,t,n,r=`request-change-detection-record`,{quiet:i=!1,keepPending:a=!1}={}){this.message.set(t);try{if(!this.rpc())throw Error(`no client`);return await hT(this.rpc(),r,{pageId:this.pageId(),...e}),a||this.message.set(``),i||this.announce(n()),!0}catch{let e=`Could not reach the page.`;return this.message.set(e),this.announce(e),!1}}announce(e){this.announcement.update(t=>t===e?`${e} `:e)}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-cd-recording`]],inputs:{livePage:[1,`page`,`livePage`],pageId:[1,`pageId`],rpc:[1,`rpc`],native:[1,`native`]},outputs:{showComponent:`showComponent`},decls:23,vars:13,consts:[[`aria-labelledby`,`cd-heading`,1,`cd`],[1,`cd-head`],[`id`,`cd-heading`],[3,`imported`,`page`,`maxCycles`],[1,`status`],[`role`,`status`,1,`sr-only`],[1,`imported`],[1,`hint`],[`what`,`cycles`,`limit`,`cdCycles`,3,`dropped`,`max`],[1,`note`],[1,`empty`],[`type`,`button`,3,`click`,`disabled`],[`aria-hidden`,`true`,1,`dot`],[`type`,`button`],[`type`,`button`,3,`click`],[1,`badge`],[1,`mono`],[`capability`,`profiler`,`missing`,``,`compact`,``,3,`page`],[`role`,`group`,`aria-labelledby`,`cd-views-label`,1,`views`],[`id`,`cd-views-label`,1,`views-label`],[`type`,`button`,1,`view`,3,`click`,`disabled`],[`capability`,`angularProfiling`,`missing`,``,`compact`,``,3,`page`],[3,`rangeChange`,`cycles`,`slowMs`,`range`],[3,`pick`,`cycles`,`names`,`scope`],[1,`spans-note`],[3,`pick`,`stats`,`scope`],[1,`cd-grid`],[`scope`,`col`],[`scope`,`col`,1,`num`],[`aria-label`,`Latest change detection cycles, newest first`,1,`cycles`],[3,`page`],[`type`,`button`,1,`name-link`,3,`click`],[1,`num`],[`colspan`,`4`],[1,`when`],[1,`trigger`],[1,`timing`],[1,`top`]],template:function(e,t){if(e&1&&(F(0,`section`,0)(1,`div`,1)(2,`h2`,2),K(3,`Change detection`),I(),A(4,IA,4,5),F(5,`app-cd-transfer`,3),H(`imported`,function(e){return t.showImport(e)}),I(),F(6,`p`,4),K(7),I(),F(8,`p`,5),K(9),I()(),A(10,LA,7,1,`p`,6),F(11,`p`,7),K(12,` Records each change detection cycle with Angular's profiler: how long it took, how many components it checked, what started it and which checks changed nothing. Tree rows show how often each instance was checked. Times come from a development build. While it records, the page also wraps Angular's scheduler, adds a signal write hook and watches DOM updates; all of it stops with the recording. `),A(13,RA,1,0)(14,zA,1,0),I(),A(15,HA,2,1),A(16,GA,16,7),L(17,`app-limit-note`,8),M(18,KA,2,1,`p`,9,tg),A(20,YA,9,10),A(21,sj,46,6)(22,cj,2,0,`p`,10),I()),e&2){let e;D(4),j(t.imported()?-1:4),D(),P(`page`,t.page())(`maxCycles`,t.maxCycles()),D(2),q(t.status()),D(2),q(t.announcement()),D(),j((e=t.imported())?10:-1,e),D(3),j(t.native()?13:14),D(2),j(t.page()?.supported===!1?15:-1),D(),j(!t.imported()&&!t.native()?16:-1),D(),P(`dropped`,t.page()?.dropped??0)(`max`,t.maxCycles()),D(),N(t.capNotes()),D(2),j(t.cycles().length?20:-1),D(),j(t.page()?.components?.length?21:t.recording()?22:-1)}},dependencies:[bD,aO,yA,jk,SA,qk,cA,NA],styles:[`[_nghost-%COMP%] { + display: block; + margin-top: 16px; +} + +.cd[_ngcontent-%COMP%] { + border: 1px solid var(--%NS%border); + border-radius: var(--%NS%radius); + background: var(--%NS%surface); + padding: 12px 14px; +} + +.cd-head[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px 12px; +} + +h2[_ngcontent-%COMP%] { + margin: 0; + font-size: 14px; + color: var(--%NS%text-strong); +} + +h3[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + margin: 12px 0 6px; +} + +button[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; + gap: 8px; + height: var(--%NS%control-h); + padding: 0 12px; + background: var(--%NS%surface-2); + border: 1px solid var(--%NS%control-border); + border-radius: var(--%NS%radius-sm); + color: var(--%NS%text); + cursor: pointer; + font: inherit; + font-size: 13px; +} + +button[_ngcontent-%COMP%]:hover:not(:disabled) { + background: var(--%NS%surface-3); + border-color: var(--%NS%accent-line); +} + +button[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +button[_ngcontent-%COMP%]:disabled { + cursor: not-allowed; + opacity: 0.6; +} + +.views[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-top: 8px; +} + +.views-label[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +button.view[aria-pressed=true][_ngcontent-%COMP%] { + border-color: var(--%NS%accent-line); +} + +button.view[aria-pressed=true][_ngcontent-%COMP%] .dot[_ngcontent-%COMP%] { + background: var(--%NS%accent); +} + +.dot[_ngcontent-%COMP%] { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--%NS%text-3); +} + +button.on[_ngcontent-%COMP%] .dot[_ngcontent-%COMP%] { + background: var(--%NS%danger); +} + +.status[_ngcontent-%COMP%] { + margin: 0; + color: var(--%NS%text-2); + font-size: 12px; +} + +.hint[_ngcontent-%COMP%], +.note[_ngcontent-%COMP%], +.spans-note[_ngcontent-%COMP%], +.empty[_ngcontent-%COMP%] { + margin: 8px 0; + color: var(--%NS%text-2); + line-height: 1.5; +} + +.cd-grid[_ngcontent-%COMP%] { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 0 16px; +} + +@media (min-width: 880px) { + .cd-grid[_ngcontent-%COMP%] { + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + } +} +table[_ngcontent-%COMP%] { + width: 100%; + border-collapse: collapse; + font-size: 12px; +} + +th[_ngcontent-%COMP%], +td[_ngcontent-%COMP%] { + padding: 4px 6px; + border-bottom: 1px solid var(--%NS%border); + text-align: left; +} + +th[_ngcontent-%COMP%] { + color: var(--%NS%text-2); + font-weight: 500; +} + +td.mono[_ngcontent-%COMP%] { + overflow-wrap: anywhere; +} + +.name-link[_ngcontent-%COMP%] { + height: auto; + min-height: 0; + padding: 0; + border: 0; + background: none; + color: var(--%NS%text-strong); + font: inherit; + text-align: left; + text-decoration: underline; + text-underline-offset: 2px; + cursor: pointer; +} + +.name-link[_ngcontent-%COMP%]:hover { + background: none; + text-decoration-thickness: 2px; +} + +.name-link[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: -2px; +} + +.num[_ngcontent-%COMP%] { + text-align: right; + font-variant-numeric: tabular-nums; +} + +.mono[_ngcontent-%COMP%] { + font-family: var(--%NS%font-mono); +} + +.cycles[_ngcontent-%COMP%] { + margin: 0; + padding: 0; + list-style: none; + font-size: 12px; +} + +.cycles[_ngcontent-%COMP%] li[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + gap: 4px 10px; + padding: 4px 0; + border-bottom: 1px solid var(--%NS%border); + color: var(--%NS%text); +} + +.when[_ngcontent-%COMP%], +.trigger[_ngcontent-%COMP%], +.timing[_ngcontent-%COMP%], +.top[_ngcontent-%COMP%] { + color: var(--%NS%text-2); +} + +.imported[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px 12px; + margin: 8px 0; + color: var(--%NS%text-2); +} + +.badge[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%accent) 30%, transparent); + background: color-mix(in srgb, var(--%NS%accent) 12%, transparent); + color: var(--%NS%accent-text); + padding: 2px 8px; + border: 1px solid; + border-radius: var(--%NS%radius-sm); + font-size: 12px; + font-weight: 600; +} + +.trigger[_ngcontent-%COMP%] .mono[_ngcontent-%COMP%], +.top[_ngcontent-%COMP%] .mono[_ngcontent-%COMP%] { + overflow-wrap: anywhere; +}`]})};function fj(e){return Object.fromEntries(e.map(e=>[e.name,e.value??null]))}function pj(e){return e.map(e=>({name:e.name,listened:!!e.listened}))}function mj(e){let t={component:e.name,tag:e.tag,hostPath:e.path};e.selector&&(t.selector=e.selector),e.source&&(t.source=e.source),e.changeDetection&&(t.changeDetection=e.changeDetection),e.encapsulation&&(t.encapsulation=e.encapsulation),t.inputs=fj(e.inputs),t.outputs=pj(e.outputs),t.properties=fj(e.properties??[]),e.listeners.length&&(t.listeners=e.listeners),e.directives.length&&(t.directives=e.directives.map(e=>({name:e.name,inputs:fj(e.inputs),outputs:pj(e.outputs)}))),t.injected=e.dependencies.map(e=>({token:e.token,providedBy:e.providedBy?e.providedByName??`an injector`:null}));try{return JSON.stringify(t,null,2)}catch{return JSON.stringify({component:e.name,tag:e.tag,hostPath:e.path})}}function hj(e){return e.selector||e.path}var gj=1e3;function _j(e,t){let n=[];for(let[r,i]of t){let t=e.get(r);t!==void 0&&t!==i&&n.push(r)}return n}function vj(e){try{return JSON.stringify(e)??`undefined`}catch{return String(e)}}var yj=class{ms;changed=T(new Set);owner=null;last=new Map;timers=new Map;constructor(e=gj){this.ms=e}update(e,t){let n=new Map([...t].map(([e,t])=>[e,vj(t)]));if(e!==this.owner){this.clear(),this.owner=e,this.last=n;return}let r=_j(this.last,n);if(this.last=n,r.length){this.changed.update(e=>new Set([...e,...r]));for(let e of r)clearTimeout(this.timers.get(e)),this.timers.set(e,setTimeout(()=>{this.timers.delete(e),this.changed.update(t=>{let n=new Set(t);return n.delete(e),n})},this.ms))}}clear(){for(let e of this.timers.values())clearTimeout(e);this.timers.clear(),this.changed().size&&this.changed.set(new Set)}};function bj(e,t,n=0){return e===`directive`?`directive:${n}:${t}`:`${e}:${t}`}var xj=[`*`];function Sj(e,t){e&1&&K(0),e&2&&q(W(2).label())}function Cj(e,t){e&1&&jg(0,0,null,Sj,1,1)}function wj(e,t){if(e&1&&(F(0,`span`,4),K(1),I()),e&2){let e=W(2);D(),q(e.message())}}function Tj(e,t){if(e&1){let e=B();F(0,`button`,2),H(`click`,function(){return S(e),C(W().open())}),gg(1,3),I(),A(2,wj,2,1,`span`,4)}if(e&2){let e=W(),t=Fg(1);P(`title`,`Open `+e.label()+` in your editor`),k(`aria-label`,`Open `+e.label()+` in your editor`),D(),P(`ngTemplateOutlet`,t),D(),j(e.message()?2:-1)}}function Ej(e,t){if(e&1&&(F(0,`span`,1),gg(1,3),I()),e&2){let e=W(),t=Fg(1);P(`title`,e.label()),D(),P(`ngTemplateOutlet`,t)}}var Dj=class e{source=T(T(null));client=X(()=>this.source()());available=X(()=>{let e=this.client();return!!e&&!_T(e)});use(e){this.source.set(e)}async open(e,t){if(!this.available())return{ok:!1,error:`No server is connected.`};try{let n=await hT(this.client(),`open-in-editor`,{file:e,...t?{line:t}:{}});return n?.ok?{ok:!0}:{ok:!1,error:n?.error??`The server did not open it.`}}catch{return{ok:!1,error:`The server did not open it.`}}}static ɵfac=function(t){return new(t||e)};static ɵprov=kl({token:e,factory:e.ɵfac})};function Oj(e,t){return t?`${e}:${t}`:e}var kj=class e{editor=x(Dj);file=$.required();line=$(void 0);label=X(()=>Oj(this.file(),this.line()));message=T(``);async open(){this.message.set(``);let e=await this.editor.open(this.file(),this.line()??void 0);e.ok||this.message.set(`Not opened: ${e.error}`)}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-source-link`]],inputs:{file:[1,`file`],line:[1,`line`]},ngContentSelectors:xj,decls:4,vars:1,consts:[[`text`,``],[1,`source-text`,3,`title`],[`type`,`button`,1,`source-link`,3,`click`,`title`],[3,`ngTemplateOutlet`],[`role`,`alert`,1,`message`]],template:function(e,t){e&1&&(Ag(),Mh(0,Cj,2,0,`ng-template`,null,0,nv),A(2,Tj,3,4)(3,Ej,2,2,`span`,1)),e&2&&(D(2),j(t.editor.available()?2:3))},dependencies:[Qv],styles:[`[_nghost-%COMP%] { + display: inline; + min-width: 0; +} + +.source-link[_ngcontent-%COMP%] { + display: inline; + padding: 0; + border: 0; + border-radius: 2px; + background: none; + color: inherit; + font: inherit; + text-align: inherit; + text-decoration: underline dotted; + text-underline-offset: 2px; + cursor: pointer; + overflow-wrap: anywhere; +} + +.source-link[_ngcontent-%COMP%]:hover { + color: var(--%NS%accent-text); + text-decoration-style: solid; +} + +.source-link[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.message[_ngcontent-%COMP%] { + margin-left: 6px; + color: var(--%NS%danger-text); + font-family: var(--%NS%font-sans); + font-size: 0.92em; +}`]})},Aj=[`field`],jj=[`editButton`],Mj=[`valueBox`];function Nj(e,t){if(e&1){let e=B();R(0,`div`,5)(1,`label`,6),K(2),z(),R(3,`input`,7,0),U(`input`,function(t){return S(e),C(W().draft.set(t.target.value))})(`keydown.enter`,function(t){S(e);let n=W();return t.preventDefault(),C(n.apply())})(`keydown.escape`,function(t){S(e);let n=W();return t.preventDefault(),t.stopPropagation(),C(n.cancel())}),z(),R(5,`button`,8),U(`click`,function(){return S(e),C(W().apply())}),K(6,`Apply`),z(),R(7,`button`,9),U(`click`,function(){return S(e),C(W().cancel())}),K(8,`Cancel`),z()(),R(9,`p`,10),K(10),z()}if(e&2){let e=W();D(),V(`htmlFor`,e.fieldId),D(),J(`New value of `,e.prop().name,`, as JSON`),D(),V(`id`,e.fieldId)(`value`,e.draft())(`readOnly`,e.busy()),k(`aria-busy`,e.busy()?`true`:null)(`aria-invalid`,e.error()?`true`:null)(`aria-describedby`,e.hintId+(e.error()?` `+e.errorId:``)),D(2),V(`disabled`,e.busy()),D(4),V(`id`,e.hintId),D(),J(` JSON, such as "text", 42, true or `,e.objectSample,`. Enter applies, Escape cancels. `)}}function Pj(e,t){if(e&1&&(R(0,`pre`,null,1),K(2),Q_(3,`json`),z()),e&2){let e=W(2);k(`tabindex`,e.scrollable()?0:null)(`role`,e.scrollable()?`region`:null)(`aria-label`,e.scrollable()?`Value of `+e.prop().name:null),D(2),q($_(3,4,e.prop().value))}}function Fj(e,t){if(e&1){let e=B();R(0,`button`,14,2),U(`click`,function(){return S(e),C(W(2).start())}),K(2,` Edit `),z()}if(e&2){let e=W(2);V(`disabled`,e.busy()),k(`aria-label`,`Edit `+e.prop().name)}}function Ij(e,t){if(e&1&&(R(0,`p`,13),K(1),z()),e&2){let e=W(2);D(),q(e.prop().locked)}}function Lj(e,t){if(e&1&&(R(0,`div`,11),A(1,Pj,4,6,`pre`),A(2,Fj,3,2,`button`,12),z(),A(3,Ij,2,1,`p`,13)),e&2){let e=W();D(),j(e.bare()?-1:1),D(),j(e.editable()?2:-1),D(),j(e.writes()&&e.prop().locked?3:-1)}}function Rj(e,t){if(e&1&&(R(0,`p`,3),K(1),z()),e&2){let e=W();V(`id`,e.errorId),D(),q(e.error())}}var zj=0;function Bj(e){let t=e.trim();if(!t)return{error:`Type a JSON value, such as "text", 42, true, null or [1, 2].`};try{return{value:JSON.parse(t)}}catch{return{error:`Not valid JSON. Put text in double quotes, such as "Paris".`}}}var Vj=class e{prop=$.required();componentId=$.required();pageId=$(null);rpc=$(null);writes=$(!1);bare=$(!1);readDraft=$(null);fieldId=`prop-edit-${++zj}`;hintId=`${this.fieldId}-hint`;errorId=`${this.fieldId}-error`;objectSample=`{"a": 1}`;editing=T(!1);draft=T(``);busy=T(!1);error=T(``);done=T(``);editable=X(()=>this.writes()&&!!this.prop().edit&&!!this.pageId()&&!!this.rpc());field=gv(`field`);editButton=gv(`editButton`);valueBox=gv(`valueBox`);injector=x(bs);scrollable=T(!1);constructor(){Cv(()=>{this.prop();let e=this.valueBox()?.nativeElement;this.scrollable.set(!!e&&e.scrollHeight>e.clientHeight+1)})}async start(){let e=this.prop().value;this.error.set(``),this.done.set(``);let t=this.readDraft();if(t){this.busy.set(!0);let n=await t();if(this.busy.set(!1),n.error!==void 0){this.error.set(n.error);return}e=n.value}this.draft.set(e===void 0?``:JSON.stringify(e)),this.editing.set(!0),cd(()=>{let e=this.field()?.nativeElement;e?.focus(),e?.select()},{injector:this.injector})}cancel(){this.editing()&&(this.editing.set(!1),this.busy.set(!1),this.error.set(``),this.focusEdit())}async apply(){if(this.busy())return;let e=Bj(this.draft());if(`error`in e){this.error.set(e.error);return}let t=this.prop(),n=this.rpc(),r=this.pageId();if(!n||!r||!t.edit)return;this.busy.set(!0),this.error.set(``);let i;try{i=await n.scope(`pangular`).rpc.call(`request-component-edit`,{pageId:r,request:{id:this.componentId(),path:t.prop,value:e.value,kind:t.edit}})??{}}catch{i={error:`The devtools server did not answer. Try again.`}}this.busy.set(!1),this.editing()&&(i.ok?(this.editing.set(!1),this.done.set(`Set ${t.name}.`),this.focusEdit()):(this.error.set(i.error??`The page did not write it.`),this.field()?.nativeElement.focus()))}focusEdit(){cd(()=>this.editButton()?.nativeElement.focus(),{injector:this.injector})}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-prop-value`]],viewQuery:function(e,t){e&1&&Ng(t.field,Aj,5)(t.editButton,jj,5)(t.valueBox,Mj,5),e&2&&Pg(3)},inputs:{prop:[1,`prop`],componentId:[1,`componentId`],pageId:[1,`pageId`],rpc:[1,`rpc`],writes:[1,`writes`],bare:[1,`bare`],readDraft:[1,`readDraft`]},decls:5,vars:3,consts:[[`field`,``],[`valueBox`,``],[`editButton`,``],[`role`,`alert`,1,`error`,3,`id`],[`role`,`status`,1,`sr-only`],[1,`editor`],[1,`sr-only`,3,`for`],[`type`,`text`,`autocomplete`,`off`,`spellcheck`,`false`,1,`field`,`mono`,3,`input`,`keydown.enter`,`keydown.escape`,`id`,`value`,`readOnly`],[`type`,`button`,1,`primary`,3,`click`,`disabled`],[`type`,`button`,3,`click`],[1,`hint`,3,`id`],[1,`view`],[`type`,`button`,1,`edit`,3,`disabled`],[1,`locked`],[`type`,`button`,1,`edit`,3,`click`,`disabled`]],template:function(e,t){e&1&&(A(0,Nj,11,11)(1,Lj,4,3),A(2,Rj,2,2,`p`,3),R(3,`p`,4),K(4),z()),e&2&&(j(+!t.editing()),D(2),j(t.error()?2:-1),D(2),q(t.done()))},dependencies:[ey],styles:[`[_nghost-%COMP%] { + display: block; + min-width: 0; +} + +.view[_ngcontent-%COMP%] { + display: flex; + align-items: flex-start; + gap: 8px; +} + +pre[_ngcontent-%COMP%] { + flex: 1 1 auto; + min-width: 0; + max-height: 200px; + margin: 0; + overflow: auto; + color: var(--%NS%text); + font-family: var(--%NS%font-mono); + font-size: 12px; + line-height: 1.5; + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.editor[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + gap: 6px; + align-items: center; +} + +.field[_ngcontent-%COMP%] { + flex: 1 1 160px; + min-width: 0; + height: var(--%NS%control-h); + padding: 0 10px; + background: var(--%NS%bg); + border: 1px solid var(--%NS%control-border); + border-radius: var(--%NS%radius-sm); + color: var(--%NS%text); + font-family: var(--%NS%font-mono); + font-size: 12px; +} + +.field[_ngcontent-%COMP%]:focus-visible { + outline: none; + border-color: var(--%NS%accent); + box-shadow: 0 0 0 3px var(--%NS%accent-soft); +} + +.field[aria-invalid=true][_ngcontent-%COMP%] { + border-color: var(--%NS%danger); +} + +button[_ngcontent-%COMP%] { + display: inline-flex; + flex: none; + align-items: center; + height: var(--%NS%control-h); + padding: 0 12px; + background: var(--%NS%surface-2); + border: 1px solid var(--%NS%control-border); + border-radius: var(--%NS%radius-sm); + color: var(--%NS%text); + cursor: pointer; + font: inherit; + font-size: 13px; + font-weight: 500; + transition: background-color 150ms var(--%NS%ease), border-color 150ms var(--%NS%ease); +} + +button[_ngcontent-%COMP%]:hover:not(:disabled) { + background: var(--%NS%surface-3); + border-color: var(--%NS%accent-line); +} + +button[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +pre[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: -2px; +} + +button[_ngcontent-%COMP%]:disabled { + opacity: 0.6; +} + +button.primary[_ngcontent-%COMP%] { + background: var(--%NS%accent); + border-color: var(--%NS%accent); + color: var(--%NS%accent-ink); +} + +button.primary[_ngcontent-%COMP%]:hover:not(:disabled) { + background: var(--%NS%accent-hover); + border-color: var(--%NS%accent-hover); +} + +button.edit[_ngcontent-%COMP%] { + height: 26px; + padding: 0 10px; + font-size: 12px; +} + +.hint[_ngcontent-%COMP%], +.locked[_ngcontent-%COMP%], +.error[_ngcontent-%COMP%] { + margin: 6px 0 0; + font-size: 12px; + line-height: 1.4; +} + +.hint[_ngcontent-%COMP%], +.locked[_ngcontent-%COMP%] { + color: var(--%NS%text-2); +} + +.error[_ngcontent-%COMP%] { + color: var(--%NS%danger-text); +}`]})},Hj=1e3,Uj={depth:5,limit:100},Wj=`[redacted]`;function Gj(e){return!!e&&typeof e==`object`&&!Array.isArray(e)}function Kj(e){return Gj(e)&&typeof e.$more==`number`&&Object.keys(e).length===1}function qj(e){return Gj(e)&&typeof e.$collapsed==`string`&&typeof e.size==`number`&&Object.keys(e).length===2}function Jj(e){return e===`Array`?`array`:e===`Map`?`map`:e===`Set`?`set`:`object`}function Yj(e){return e.$type===`Map`&&Array.isArray(e.entries)}function Xj(e){return e.$type===`Set`&&Array.isArray(e.values)}function Zj(e){return Object.keys(e).filter(e=>e!==`$type`&&e!==`$more`)}function Qj(e){if(!e||typeof e!=`object`)return null;if(qj(e))return e.size<=0?null:{kind:Jj(e.$collapsed),type:e.$collapsed,size:e.size,collapsed:!0};if(Kj(e))return null;if(Array.isArray(e)){let t=e.at(-1),n=Kj(t)?t.$more:0,r=e.length-+!!n+n;return r?{kind:`array`,type:`Array`,size:r,collapsed:!1}:null}let t=e;if(Yj(t)||Xj(t)){let e=typeof t.size==`number`?t.size:0,n=Yj(t)?`map`:`set`;return e?{kind:n,type:n===`map`?`Map`:`Set`,size:e,collapsed:!1}:null}let n=typeof t.$more==`number`?t.$more:0,r=Zj(t).length+n,i=typeof t.$type==`string`?t.$type:`Object`;return r?{kind:`object`,type:i,size:r,collapsed:!1}:null}function $j(e){if(typeof e==`string`)return JSON.stringify(e);let t=Qj(e);if(t)return nM(t);try{return JSON.stringify(e)??String(e)}catch{return String(e)}}function eM(e){let t=Qj(e);if(!t||t.collapsed)return{children:[],more:0};if(Array.isArray(e)){let n=(Kj(e.at(-1))?e.slice(0,-1):e).map((e,t)=>({key:String(t),label:String(t),value:e}));return{children:n,more:t.size-n.length}}let n=e;if(t.kind===`map`){let e=n.entries.map((e,t)=>{let[n,r]=Array.isArray(e)?e:[void 0,e];return{key:String(t),label:$j(n),value:r}});return{children:e,more:Math.max(0,t.size-e.length)}}if(t.kind===`set`){let e=n.values.map((e,t)=>({key:String(t),label:String(t),value:e}));return{children:e,more:Math.max(0,t.size-e.length)}}return{children:Zj(n).map(e=>({key:e,label:e,value:n[e]})),more:typeof n.$more==`number`?n.$more:0}}function tM(e,t,n){return`${e} ${e===1?t:n}`}function nM(e){let t={array:tM(e.size,`item`,`items`),object:tM(e.size,`key`,`keys`),map:tM(e.size,`entry`,`entries`),set:tM(e.size,`value`,`values`)}[e.kind];return`${e.type} (${t})`}function rM(e,t){return JSON.stringify(t===void 0?e:[t,...e])}function iM(e,t){return e.map((e,n)=>n===0?e:(t?t[n-1]!==`object`:/^\d+$/.test(e))?`[${e}]`:`.${e}`).join(``)}function aM(e){return e.every(e=>e&&e===e.trim()&&!/[.[\]]/.test(e))?e.join(`.`):null}var oM=/^\[(Getter|Unreadable|Circular|Truncated|Function\b.*|Promise)\]$/;function sM(e){return e===null||typeof e==`boolean`?!0:typeof e==`number`?Number.isFinite(e):typeof e==`string`&&e!==Wj&&!oM.test(e)&&!e.endsWith(`…`)}function cM(e,t){return!e||t.kind===`map`||t.kind===`set`||e===`signal`&&t.kind===`object`&&t.type!==`Object`?null:e}function lM(e,t,n,r,i=500){let a=[],o=r.trim().toLowerCase();if(!o)return a;let s=new Set,c=(e,t)=>{let r=rM(e);if(!(s.has(r)||a.length>=i)){s.add(r);for(let r of eM(t).children){let t=[...e,r.key];if(r.label.toLowerCase().includes(o)&&a.push(t),!Qj(r.value))continue;let i=n(t)??(Qj(r.value)?.collapsed?void 0:r.value);i!==void 0&&c(t,i)}}};return c(e,n(e)??t),a}var uM=(e,t)=>t.id;function dM(e,t){if(e&1){let e=B();F(0,`button`,2),H(`click`,function(){S(e);let t=W().$implicit;return C(W(3).toggle(t))}),w(),F(1,`svg`,3),L(2,`path`,4),I(),fs(),F(3,`span`,14),K(4),I(),F(5,`span`,5),K(6),I()()}if(e&2){let e=W().$implicit;k(`aria-expanded`,e.open)(`aria-label`,e.label+`: `+e.text),D(4),J(``,e.label,`:`),D(2),q(e.text)}}function fM(e,t){if(e&1&&L(0,`app-prop-value`,15),e&2){let e=W(5);P(`prop`,t)(`componentId`,e.componentId())(`pageId`,e.pageId())(`rpc`,e.rpc())(`writes`,e.writes())}}function pM(e,t){if(e&1&&(F(0,`span`,16),K(1),I()),e&2){let e=W(2).$implicit;D(),q(e.text)}}function mM(e,t){if(e&1&&(F(0,`span`,14),K(1),I(),A(2,fM,1,5,`app-prop-value`,15)(3,pM,2,1,`span`,16)),e&2){let e,t=W().$implicit;D(),J(``,t.label,`:`),D(),j((e=t.edit)?2:3,e)}}function hM(e,t){if(e&1){let e=B();F(0,`button`,17),H(`click`,function(){S(e);let t=W().$implicit;return C(W(3).more(t))}),K(1),F(2,`span`,18),K(3),I()()}if(e&2){let e=W().$implicit,t=W(3);D(),J(` Show `,t.step(e),` more `),D(2),J(`(`,e.more,` not listed)`)}}function gM(e,t){e&1&&(F(0,`span`,12),K(1,`Reading from the page…`),I())}function _M(e,t){if(e&1&&(F(0,`span`,13),K(1),I()),e&2){let e=W().$implicit;D(),q(e.text)}}function vM(e,t){if(e&1&&(F(0,`li`,9),A(1,dM,7,4,`button`,10)(2,mM,4,2)(3,hM,4,2,`button`,11)(4,gM,2,0,`span`,12)(5,_M,2,1,`span`,13),I()),e&2){let e,n=t.$implicit;n_(`padding-left`,n.depth*14,`px`),D(),j((e=n.kind)===`node`?1:e===`leaf`?2:e===`more`?3:e===`loading`?4:e===`error`?5:-1)}}function yM(e,t){if(e&1&&(F(0,`ul`,7),M(1,vM,6,3,`li`,8,uM),I()),e&2){let e=W(2);P(`id`,e.listId),k(`aria-label`,e.prop().name+` entries`),D(),N(e.rows())}}function bM(e,t){if(e&1){let e=B();F(0,`div`,1)(1,`button`,2),H(`click`,function(){return S(e),C(W().toggleRoot())}),w(),F(2,`svg`,3),L(3,`path`,4),I(),fs(),F(4,`span`,5),K(5),I()(),L(6,`app-prop-value`,6),I(),A(7,yM,3,2,`ul`,7)}if(e&2){let e=t,n=W();D(),k(`aria-expanded`,n.rootOpen())(`aria-controls`,n.rootOpen()?n.listId:null)(`aria-label`,n.prop().name+`: `+n.summaryOf(e)),D(4),q(n.summaryOf(e)),D(),P(`bare`,!0)(`prop`,n.prop())(`componentId`,n.componentId())(`pageId`,n.pageId())(`rpc`,n.rpc())(`writes`,n.writes())(`readDraft`,n.readWhole),D(),j(n.rootOpen()?7:-1)}}function xM(e,t){if(e&1&&L(0,`app-prop-value`,0),e&2){let e=W();P(`prop`,e.prop())(`componentId`,e.componentId())(`pageId`,e.pageId())(`rpc`,e.rpc())(`writes`,e.writes())}}var SM=0;function CM(e){if(e===void 0)return`undefined`;try{return JSON.stringify(e)??String(e)}catch{return String(e)}}function wM(e){if(Array.isArray(e))return e.every(wM);if(e&&typeof e==`object`){let t=e;return![`$collapsed`,`$more`,`$type`].some(e=>Object.hasOwn(t,e))&&Object.values(t).every(wM)}return sM(e)}var TM=class e{prop=$.required();store=$.required();componentId=$.required();pageId=$(null);rpc=$(null);writes=$(!1);directive=$(void 0);query=$(``);listId=`value-tree-${++SM}`;host=x(Ml);pendingMore=null;constructor(){Cv(()=>{let e=this.rows(),t=this.pendingMore;if(!t)return;let n=e.findIndex(e=>(e.kind===`leaf`||e.kind===`node`)&&!t.has(e.id));if(n<0)return;this.pendingMore=null;let r=document.activeElement;r&&r!==document.body&&r.isConnected||this.focusNear(n)})}rootPath=X(()=>[this.prop().prop]);rootValue=X(()=>this.store().level(this.rootPath(),this.directive())??this.prop().value);root=X(()=>Qj(this.rootValue()));needle=X(()=>this.query().trim().toLowerCase());matches=X(()=>{let e=this.needle(),t=this.prop();if(!e||[t.name,t.prop].some(t=>t.toLowerCase().includes(e)))return null;let n=this.store(),r=this.directive();return lM(this.rootPath(),this.rootValue(),e=>n.level(e,r),e)});rootOpen=X(()=>!!this.matches()?.length||this.store().isOpen(this.rootPath(),this.directive()));rows=X(()=>{let e=this.root();if(!e)return[];let t=this.store(),n=this.directive(),r=this.matches(),i=new Set,a=new Set;for(let e of r??[]){a.add(rM(e));for(let t=2;t<=e.length;t++)i.add(rM(e.slice(0,t)))}let o=this.prop(),s=this.writes()&&n===void 0,c=[],l=(e,o,d,f,p,m)=>{let{children:ee,more:h}=eM(e);for(let e of ee){let ee=[...o,e.key],h=rM(ee);if(r&&!m&&!i.has(h))continue;let g=Qj(e.value),te=rM(ee,n);if(!g){let t=p&&sM(e.value)?aM(ee):null;c.push({id:te,kind:`leaf`,depth:f,path:ee,label:e.label,text:CM(e.value),edit:s&&t&&p?{name:iM(ee,[...d]),prop:t,value:e.value,edit:p}:void 0});continue}let ne=!!r&&!m&&i.has(h)&&!a.has(h)||t.isOpen(ee,n);if(c.push({id:te,kind:`node`,depth:f,path:ee,label:e.label,text:nM(g),container:g,open:ne}),!ne)continue;let re=t.level(ee,n)??(g.collapsed?void 0:e.value),ie=[...d,g.kind];re===void 0?t.errors().has(te)?c.push({...u(ee,f+1,`error`),text:t.errors().get(te)}):c.push(u(ee,f+1,`loading`)):l(re,ee,ie,f+1,cM(p,g),m||a.has(h))}h>0&&(!r||m)&&c.push({...u(o,f,`more`),more:h,listed:ee.length})},u=(e,t,r)=>({id:`${rM(e,n)}:${r}`,kind:r,depth:t,path:e,label:``,text:``});return l(this.rootValue(),this.rootPath(),[e.kind],0,s?cM(o.editInside??null,e):null,!r),c});readWhole=async()=>{let e=await this.store().read(this.rootPath(),Uj.depth,Uj.limit);return e.ok?wM(e.value)?{value:e.value}:{error:`Too large to edit as a whole. Open it to edit the values inside.`}:{error:e.error??`The page did not read it.`}};summaryOf(e){return nM(e)}toggleRoot(){this.pendingMore=null,this.store().toggle(this.rootPath(),this.directive(),!1)}toggle(e){this.pendingMore=null,this.store().toggle(e.path,this.directive(),!!e.container?.collapsed)}more(e){this.pendingMore=new Set(this.rows().map(e=>e.id)),this.store().more(e.path,this.directive(),e.listed??0)}focusNear(e){let t=this.host.nativeElement,n=Array.from(t.querySelectorAll(`#${this.listId} > li`)),r=e=>e?.querySelector(`button:not([disabled]), input, textarea, [tabindex="0"]`);(n.slice(e).map(r).find(Boolean)??n.slice(0,e).reverse().map(r).find(Boolean)??t.querySelector(`.root > .toggle`))?.focus()}step(e){return Math.min(100,e.more??0)}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-value-tree`]],inputs:{prop:[1,`prop`],store:[1,`store`],componentId:[1,`componentId`],pageId:[1,`pageId`],rpc:[1,`rpc`],writes:[1,`writes`],directive:[1,`directive`],query:[1,`query`]},decls:2,vars:1,consts:[[3,`prop`,`componentId`,`pageId`,`rpc`,`writes`],[1,`root`],[`type`,`button`,1,`toggle`,3,`click`],[`viewBox`,`0 0 24 24`,`aria-hidden`,`true`,1,`chevron`],[`d`,`m9 6 6 6-6 6`],[1,`summary`,`mono`],[1,`whole`,3,`bare`,`prop`,`componentId`,`pageId`,`rpc`,`writes`,`readDraft`],[1,`entries`,3,`id`],[1,`entry`,3,`padding-left`],[1,`entry`],[`type`,`button`,1,`toggle`],[`type`,`button`,1,`more`],[`role`,`status`,1,`muted`],[`role`,`alert`,1,`error`],[1,`key`,`mono`],[1,`leaf-edit`,3,`prop`,`componentId`,`pageId`,`rpc`,`writes`],[1,`leaf`,`mono`],[`type`,`button`,1,`more`,3,`click`],[1,`muted`]],template:function(e,t){if(e&1&&A(0,bM,8,12)(1,xM,1,5,`app-prop-value`,0),e&2){let e;j((e=t.root())?0:1,e)}},dependencies:[Vj],styles:[`[_nghost-%COMP%] { + display: block; + min-width: 0; +} + +.root[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 8px; +} + +.whole[_ngcontent-%COMP%] { + flex: 1 1 auto; +} + +.entries[_ngcontent-%COMP%] { + margin: 4px 0 0; + padding: 0 0 0 6px; + border-left: 1px solid var(--%NS%border); + list-style: none; +} + +.entry[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 6px; + min-width: 0; + padding-top: 2px; + padding-bottom: 2px; + font-size: 12px; + line-height: 1.5; +} + +.toggle[_ngcontent-%COMP%], +.more[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; + gap: 4px; + min-height: 24px; + max-width: 100%; + padding: 0 6px 0 2px; + background: none; + border: 1px solid transparent; + border-radius: var(--%NS%radius-sm); + color: var(--%NS%text); + cursor: pointer; + font: inherit; + font-size: 12px; + text-align: left; + transition: background-color 150ms var(--%NS%ease); +} + +.toggle[_ngcontent-%COMP%]:hover, +.more[_ngcontent-%COMP%]:hover { + background: var(--%NS%surface-2); +} + +.toggle[_ngcontent-%COMP%]:focus-visible, +.more[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.more[_ngcontent-%COMP%] { + padding-left: 6px; + color: var(--%NS%text); + text-decoration: underline; +} + +.chevron[_ngcontent-%COMP%] { + flex: none; + width: 14px; + height: 14px; + fill: none; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + transition: transform 150ms var(--%NS%ease); +} + +.toggle[aria-expanded=true][_ngcontent-%COMP%] .chevron[_ngcontent-%COMP%] { + transform: rotate(90deg); +} + +.key[_ngcontent-%COMP%] { + flex: none; + max-width: 50%; + overflow-wrap: anywhere; + color: var(--%NS%text-2); +} + +.summary[_ngcontent-%COMP%] { + color: var(--%NS%text-2); +} + +.leaf[_ngcontent-%COMP%] { + min-width: 0; + color: var(--%NS%text); + overflow-wrap: anywhere; +} + +.leaf-edit[_ngcontent-%COMP%] { + flex: 1 1 160px; +} + +.muted[_ngcontent-%COMP%] { + color: var(--%NS%text-2); +} + +.error[_ngcontent-%COMP%] { + color: var(--%NS%danger-text); +} + +@media (prefers-reduced-motion: reduce) { + .chevron[_ngcontent-%COMP%], + .toggle[_ngcontent-%COMP%], + .more[_ngcontent-%COMP%] { + transition: none; + } +}`]})};function EM(e,t){return e.length>t.length&&e.startsWith(`${t.slice(0,-1)},`)}var DM=class{sender;target={rpc:null,pageId:null,componentId:null};fetched=T(new Map);pushed=T({});open=T(new Set);loading=T(new Set);errors=T(new Map);limits=new Map;generation=0;constructor(e){this.sender=e}follow(e,t){let n=e.componentId===this.target.componentId&&e.pageId===this.target.pageId;this.target=e,n||(this.generation++,this.fetched.set(new Map),this.open.set(new Set),this.loading.set(new Set),this.errors.set(new Map),this.limits.clear()),this.pushed.set(t??{})}level(e,t){let n=rM(e,t),r=this.pushed();return Object.hasOwn(r,n)?r[n]:this.fetched().get(n)}isOpen(e,t){return this.open().has(rM(e,t))}toggle(e,t,n){let r=rM(e,t),i=new Set(this.open());if(i.has(r)){for(let e of i)(e===r||EM(e,r))&&i.delete(e);this.open.set(i),this.send({path:e,directive:t,forget:!0});return}i.add(r),this.open.set(i),n&&this.load(e,t,this.limits.get(r)??30)}more(e,t,n){let r=rM(e,t),i=Math.min(Hj,Math.max(n,this.limits.get(r)??0)+100);this.limits.set(r,i),this.load(e,t,i)}async read(e,t,n){return this.send({path:e,depth:t,limit:n})}async load(e,t,n){let r=rM(e,t),i=this.generation;this.loading.update(e=>new Set(e).add(r));let a=await this.send({path:e,directive:t,limit:n,watch:!0});i===this.generation&&(this.loading.update(e=>{let t=new Set(e);return t.delete(r),t}),this.errors.update(e=>{let t=new Map(e);return a.ok?t.delete(r):t.set(r,a.error??`The page did not read it.`),t}),a.ok&&this.fetched.update(e=>new Map(e).set(r,a.value)))}async send(e){if(this.sender){if(e.forget)return{ok:!0};let{directive:t,watch:n,...r}=e;try{return await this.sender(r)}catch{return{error:`The devtools server did not answer. Try again.`}}}let{rpc:t,pageId:n,componentId:r}=this.target;if(!t||!n||!r)return{error:`No live page to read from.`};let{directive:i,...a}=e;try{return await t.scope(`pangular`).rpc.call(`request-component-value`,{pageId:n,request:{id:r,...a,...i===void 0?{}:{directive:i}}})??{}}catch{return{error:`The devtools server did not answer. Try again.`}}}};function OM(e,t){if(e&1&&(R(0,`span`,1),K(1),z()),e&2){let e=W(2);D(),J(`: `,e.label())}}function kM(e,t){if(e&1&&(R(0,`span`,1),K(1),z()),e&2){let e=W(3);D(),J(`: `,e.label())}}function AM(e,t){if(e&1){let e=B();R(0,`button`,4),U(`click`,function(){return S(e),C(W(2).store(`log`))}),K(1,` Log to console `),A(2,kM,2,1,`span`,1),z()}if(e&2){let e=W(2);V(`disabled`,e.busy()||!e.rpc()||!e.pageId()),D(2),j(e.label()?2:-1)}}function jM(e,t){if(e&1){let e=B();R(0,`button`,0),U(`click`,function(){return S(e),C(W().store())}),K(1,` Store as global `),A(2,OM,2,1,`span`,1),z(),A(3,AM,3,2,`button`,2),R(4,`span`,3),K(5),z()}if(e&2){let e=W();V(`disabled`,e.busy()||!e.rpc()||!e.pageId()),D(2),j(e.label()?2:-1),D(),j(e.canLog()?3:-1),D(2),q(e.message())}}var MM=class e{rpc=$(null);pageId=$(null);target=$.required();label=$(``);canLog=$(!1);staticReport=X(()=>_T(this.rpc()));busy=T(!1);message=T(``);async store(e=`store`){let t=this.rpc(),n=this.pageId();if(t&&n){this.busy.set(!0),this.message.set(``);try{let r=await t.scope(`pangular`).rpc.call(`request-console-global`,{pageId:n,target:this.target(),...e===`log`?{mode:e}:{}}),i=e===`log`?r?.ok?`Logged in the page console.`:`Not logged.`:r?.ok&&r.name?`Stored as ${r.name}.`:`Not stored.`;this.message.set(r?.message||i)}catch{this.message.set(`Could not reach the page.`)}finally{this.busy.set(!1)}}}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-store-global`]],inputs:{rpc:[1,`rpc`],pageId:[1,`pageId`],target:[1,`target`],label:[1,`label`],canLog:[1,`canLog`]},decls:1,vars:1,consts:[[`type`,`button`,1,`store`,3,`click`,`disabled`],[1,`sr-only`],[`type`,`button`,1,`store`,`log`,3,`disabled`],[`role`,`status`,1,`status`],[`type`,`button`,1,`store`,`log`,3,`click`,`disabled`]],template:function(e,t){e&1&&A(0,jM,6,4),e&2&&j(t.staticReport()?-1:0)},styles:[`[_nghost-%COMP%] { + display: inline-flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + min-width: 0; +} + + +.store[_ngcontent-%COMP%] { + height: var(--%NS%control-h); + padding: 0 14px; + border: 1px solid var(--%NS%control-border); + border-radius: var(--%NS%radius-sm); + background: var(--%NS%surface-2); + color: var(--%NS%text); + font: inherit; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: background-color 150ms var(--%NS%ease); +} + +.store[_ngcontent-%COMP%]:hover:not(:disabled) { + background: var(--%NS%surface-3); +} + +.store[_ngcontent-%COMP%]:disabled { + cursor: not-allowed; + opacity: 0.6; +} + +.store[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +@media (prefers-reduced-motion: reduce) { + .store[_ngcontent-%COMP%] { + transition: none; + } +} +.status[_ngcontent-%COMP%] { + color: var(--%NS%text-2); + font-size: 12px; +} + +.status[_ngcontent-%COMP%]:empty { + display: none; +}`]})},NM=(e,t)=>t.block.id,PM=(e,t)=>t.kind,FM=(e,t)=>t.id;function IM(e,t){if(e&1&&K(0),e&2){let e=W().$implicit;J(` `,e.label,` `)}}function LM(e,t){if(e&1&&(K(0),R(1,`span`,18),K(2),z()),e&2){let e=W().$implicit;J(` `,e.kind,` `),D(2),J(``,e.seconds,`s`)}}function RM(e,t){if(e&1&&(R(0,`span`,9),A(1,IM,1,1)(2,LM,3,2),z()),e&2){let e=t.$implicit;r_(`note-`+e.kind),k(`title`,e.text),D(),j(e.seconds===void 0?1:2)}}function zM(e,t){if(e&1&&(R(0,`span`,9),K(1),z()),e&2){let e=W().$implicit;k(`title`,e.block.hydratedAt===void 0?`The block hydrated before the devtools attached to the page.`:null),D(),q(e.hydrated)}}function BM(e,t){e&1&&(R(0,`span`,9),K(1,`@error block`),z())}function VM(e,t){if(e&1&&(R(0,`span`,10),K(1),z()),e&2){let e=W().$implicit;D(),q(e.timings)}}function HM(e,t){e&1&&(R(0,`span`,11),K(1,`mismatch`),z())}function UM(e,t){if(e&1&&(R(0,`dt`),K(1,`Template of`),z(),R(2,`dd`,19),K(3),z()),e&2){let e=t;D(3),Y(``,e.name,` <`,e.tag,`>`)}}function WM(e,t){if(e&1&&(R(0,`dt`),K(1,`In this state since`),z(),R(2,`dd`),K(3),z()),e&2){let e=W(2).$implicit,t=W(2);D(3),q(t.clock(e.block.since))}}function GM(e,t){if(e&1&&(R(0,`dt`),K(1,`Hydrated`),z(),R(2,`dd`),K(3),z()),e&2){let e=W(2).$implicit,t=W(2);D(3),J(` `,e.block.hydratedAt===void 0?`Before the devtools attached`:t.clock(e.block.hydratedAt),` `)}}function KM(e,t){if(e&1&&(R(0,`span`,23),K(1),z()),e&2){let e=W().$implicit;D(),q(e.hydration)}}function qM(e,t){if(e&1&&(R(0,`li`)(1,`span`,22),K(2),z(),R(3,`span`,19),K(4),z(),A(5,KM,2,1,`span`,23),z()),e&2){let e=t.$implicit,n=W(5);D(2),q(n.clock(e.at)),D(2),q(e.state),D(),j(e.hydration===`not-configured`?-1:5)}}function JM(e,t){if(e&1&&(R(0,`h3`,20),K(1,`Transitions`),z(),R(2,`ol`,21),M(3,qM,6,3,`li`,null,eg),z()),e&2){let e=W(2).$implicit;D(3),N(e.block.transitions)}}function YM(e,t){if(e&1){let e=B();R(0,`li`)(1,`button`,25),U(`click`,function(){let t=S(e).$implicit;return C(W(5).showNode.emit(t.id))}),K(2),R(3,`span`,19),K(4),z()()()}if(e&2){let e=t.$implicit;D(2),J(` Show `,e.name,` `),D(2),J(`<`,e.tag,`>`)}}function XM(e,t){if(e&1&&(R(0,`h3`,20),K(1,`Hydration mismatches`),z(),R(2,`ul`,24),M(3,YM,5,2,`li`,null,FM),z()),e&2){let e=W(2).$implicit;D(3),N(e.mismatches)}}function ZM(e,t){if(e&1&&(R(0,`div`,17)(1,`dl`)(2,`dt`),K(3,`Block`),z(),R(4,`dd`,19),K(5),z(),A(6,UM,4,2),A(7,WM,4,1),A(8,GM,4,1),R(9,`dt`),K(10,`Root elements`),z(),R(11,`dd`),K(12),z(),R(13,`dt`),K(14,`Triggers`),z(),R(15,`dd`,19),K(16),z()(),A(17,JM,5,0),A(18,XM,5,0),z()),e&2){let e,t=W().$implicit;V(`id`,`defer-detail-`+t.block.id),D(5),q(t.block.id),D(),j((e=t.block.owner)?6:-1,e),D(),j(t.block.since?7:-1),D(),j(t.block.hydration===`hydrated`?8:-1),D(4),q(t.block.rootIds.length||`None rendered`),D(4),q(t.block.triggers.join(`, `)||`None`),D(),j(t.block.transitions?.length?17:-1),D(),j(t.mismatches.length?18:-1)}}function QM(e,t){if(e&1){let e=B();R(0,`li`,4)(1,`button`,5),U(`click`,function(){let t=S(e).$implicit;return C(W(2).showOwner.emit(t.block))})(`focus`,function(){let t=S(e).$implicit;return C(W(2).highlight.emit(t.block))})(`blur`,function(){return S(e),C(W(2).highlight.emit(null))})(`mouseenter`,function(){let t=S(e).$implicit;return C(W(2).highlight.emit(t.block))})(`mouseleave`,function(){return S(e),C(W(2).highlight.emit(null))}),R(2,`span`,6),K(3),z(),R(4,`span`,7),K(5),z(),M(6,RM,3,4,`span`,8,PM),A(8,zM,2,2,`span`,9),A(9,BM,2,0,`span`,9),A(10,VM,2,1,`span`,10),A(11,HM,2,0,`span`,11),R(12,`span`,12),K(13),z()(),R(14,`button`,13),U(`click`,function(){let t=S(e).$implicit;return C(W(2).toggle(t.block.id))}),w(),R(15,`svg`,14),fg(16,`path`,15),z(),fs(),R(17,`span`,16),K(18),z()(),A(19,ZM,19,9,`div`,17),z()}if(e&2){let e=t.$implicit,n=W(2);D(),k(`aria-disabled`,e.block.owner?null:`true`),D(2),q(e.block.owner?.name??`Unknown component`),D(),r_(`defer-state-`+e.block.state),D(),q(e.block.state),D(),N(e.notes),D(2),j(e.hydrated?8:-1),D(),j(e.block.hasErrorBlock?9:-1),D(),j(e.timings?10:-1),D(),j(e.mismatches.length?11:-1),D(2),q(e.block.triggers.join(`, `)||`no triggers`),D(),k(`aria-expanded`,n.isOpen(e.block.id))(`aria-controls`,n.isOpen(e.block.id)?`defer-detail-`+e.block.id:null),D(4),J(`Details of the block in `,e.block.owner?.name??`an unknown component`),D(),j(n.isOpen(e.block.id)?19:-1)}}function $M(e,t){if(e&1&&(R(0,`section`,0)(1,`h2`,1),K(2,` Defer blocks `),R(3,`span`,2),K(4),z()(),R(5,`ul`,3),M(6,QM,20,14,`li`,4,NM),z()()),e&2){let e=W();D(4),Y(``,e.rows().length,``,e.truncated()?`+`:``),D(2),N(e.rows())}}function eN(e){return e<1e3?`${e}ms`:`${Number((e/1e3).toFixed(2))}s`}function tN(e){let t=[],n=e.placeholder?.minimumTime;n!=null&&t.push(`placeholder min ${eN(n)}`);let r=[e.loading?.afterTime==null?``:`after ${eN(e.loading.afterTime)}`,e.loading?.minimumTime==null?``:`min ${eN(e.loading.minimumTime)}`].filter(Boolean);return r.length&&t.push(`loading ${r.join(`, `)}`),t.join(` · `)}var nN=class e{blocks=$([]);truncated=$(!1);clockOffset=$(0);showOwner=lv();mismatches=$([]);showNode=lv();open=T(new Set);highlight=lv();now=T(Date.now());waiting=X(()=>this.blocks().some(e=>!!e.since&&(e.state===`placeholder`||e.state===`initial`)));rows=X(()=>{let e=this.now()-this.clockOffset();return this.blocks().map(t=>({block:t,notes:oT({...t,since:t.since||e},e),timings:tN(t),mismatches:this.mismatches().filter(e=>t.rootIds.includes(e.id)),hydrated:t.hydration===`hydrated`?rT(t):``}))});isOpen(e){return this.open().has(e)}toggle(e){this.open.update(t=>{let n=new Set(t);return n.delete(e)||n.add(e),n})}clock(e){return XE(e)}constructor(){let e,t=()=>{clearInterval(e),e=void 0};E(()=>{if(!this.waiting())return t();e===void 0&&(this.now.set(Date.now()),e=setInterval(()=>this.now.set(Date.now()),1e3))}),x(Ss).onDestroy(t)}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-defer-blocks`]],inputs:{blocks:[1,`blocks`],truncated:[1,`truncated`],clockOffset:[1,`clockOffset`],mismatches:[1,`mismatches`]},outputs:{showOwner:`showOwner`,showNode:`showNode`,highlight:`highlight`},decls:1,vars:1,consts:[[`aria-labelledby`,`ct-defer-title`,1,`defer`],[`id`,`ct-defer-title`,1,`defer-title`],[1,`pill`],[1,`defer-list`],[1,`defer-item`],[`type`,`button`,1,`defer-row`,3,`click`,`focus`,`blur`,`mouseenter`,`mouseleave`],[1,`name`,`mono`],[1,`defer-state`],[1,`flag`,3,`class`],[1,`flag`],[1,`timings`],[1,`flag`,`note-failed`],[1,`triggers`,`mono`],[`type`,`button`,1,`defer-toggle`,3,`click`],[`viewBox`,`0 0 24 24`,`aria-hidden`,`true`],[`d`,`m9 6 6 6-6 6`],[1,`sr-only`],[1,`defer-detail`,3,`id`],[`aria-hidden`,`true`],[1,`mono`],[1,`detail-label`],[1,`transitions`],[1,`when`],[1,`muted`],[1,`mismatch-links`],[`type`,`button`,3,`click`]],template:function(e,t){e&1&&A(0,$M,8,2,`section`,0),e&2&&j(t.rows().length?0:-1)},styles:[`[_nghost-%COMP%] { + display: block; +} + +.defer[_ngcontent-%COMP%] { + margin-top: 16px; +} + +.defer-title[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + display: flex; + align-items: center; + gap: 6px; + margin: 0 0 8px; +} + +.pill[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 16px; + padding: 0 5px; + border-radius: 99px; + background: var(--%NS%surface-3); + color: var(--%NS%text-2); + font-size: 10px; + font-weight: 600; + letter-spacing: 0; +} + +.defer-list[_ngcontent-%COMP%] { + display: grid; + gap: 6px; + margin: 0; + padding: 0; + list-style: none; +} + +.defer-item[_ngcontent-%COMP%] { + display: grid; + grid-template-columns: minmax(0, 1fr) var(--%NS%control-h); + gap: 6px; +} + +.defer-toggle[_ngcontent-%COMP%] { + width: var(--%NS%control-h); + padding: 0; + background: var(--%NS%surface); +} + +.defer-toggle[_ngcontent-%COMP%] svg[_ngcontent-%COMP%] { + width: 14px; + height: 14px; + fill: none; + stroke: currentColor; + stroke-width: 2; + transition: transform 150ms var(--%NS%ease); +} + +.defer-toggle[aria-expanded=true][_ngcontent-%COMP%] svg[_ngcontent-%COMP%] { + transform: rotate(90deg); +} + +@media (prefers-reduced-motion: reduce) { + .defer-toggle[_ngcontent-%COMP%] svg[_ngcontent-%COMP%] { + transition: none; + } +} +.defer-toggle[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.defer-detail[_ngcontent-%COMP%] { + grid-column: 1/-1; + padding: 8px 12px; + border: 1px solid var(--%NS%border); + border-radius: var(--%NS%radius-sm); + background: var(--%NS%bg); + font-size: 12px; +} + +.defer-detail[_ngcontent-%COMP%] dl[_ngcontent-%COMP%] { + display: grid; + grid-template-columns: max-content minmax(0, 1fr); + gap: 4px 12px; + margin: 0; +} + +.defer-detail[_ngcontent-%COMP%] dt[_ngcontent-%COMP%] { + color: var(--%NS%text-2); +} + +.defer-detail[_ngcontent-%COMP%] dd[_ngcontent-%COMP%] { + min-width: 0; + margin: 0; + color: var(--%NS%text); + overflow-wrap: anywhere; +} + +.detail-label[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + margin: 10px 0 4px; +} + +.transitions[_ngcontent-%COMP%], +.mismatch-links[_ngcontent-%COMP%] { + display: grid; + gap: 4px; + margin: 0; + padding: 0; + list-style: none; +} + +.transitions[_ngcontent-%COMP%] li[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.when[_ngcontent-%COMP%], +.muted[_ngcontent-%COMP%] { + color: var(--%NS%text-2); +} + +.mismatch-links[_ngcontent-%COMP%] button[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.defer-row[_ngcontent-%COMP%] { + justify-content: flex-start; + flex-wrap: wrap; + width: 100%; + height: auto; + min-height: var(--%NS%control-h); + padding: 6px 12px; + background: var(--%NS%surface); + border-color: var(--%NS%border); + text-align: left; +} + +.defer-row[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.defer-row[aria-disabled=true][_ngcontent-%COMP%] { + cursor: default; +} + +.name[_ngcontent-%COMP%] { + min-width: 0; + color: var(--%NS%text-strong); + font-size: 12px; + font-weight: 600; + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; +} + +.mono[_ngcontent-%COMP%] { + font-family: var(--%NS%font-mono); +} + +.defer-state[_ngcontent-%COMP%] { + padding: 0 8px; + border-radius: 99px; + background: var(--%NS%surface-3); + color: var(--%NS%text-2); + font-size: 11px; + line-height: 18px; +} + +.defer-state-complete[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%ok) 30%, transparent); + background: color-mix(in srgb, var(--%NS%ok) 12%, transparent); + color: var(--%NS%ok-text); +} + +.defer-state-error[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%danger) 30%, transparent); + background: color-mix(in srgb, var(--%NS%danger) 12%, transparent); + color: var(--%NS%danger-text); +} + +.defer-state-loading[_ngcontent-%COMP%], +.defer-state-placeholder[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%warn) 30%, transparent); + background: color-mix(in srgb, var(--%NS%warn) 12%, transparent); + color: var(--%NS%warn-text); +} + +.flag[_ngcontent-%COMP%] { + padding: 0 6px; + border: 1px solid var(--%NS%border-strong); + border-radius: 5px; + color: var(--%NS%text-2); + font-family: var(--%NS%font-mono); + font-size: 10px; + line-height: 16px; +} + +.note-failed[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%danger) 30%, transparent); + background: color-mix(in srgb, var(--%NS%danger) 12%, transparent); + color: var(--%NS%danger-text); +} + +.note-stuck[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%warn) 30%, transparent); + background: color-mix(in srgb, var(--%NS%warn) 12%, transparent); + color: var(--%NS%warn-text); +} + +.timings[_ngcontent-%COMP%] { + color: var(--%NS%text-2); + font-size: 11px; +} + +.triggers[_ngcontent-%COMP%] { + min-width: 0; + color: var(--%NS%text-2); + font-size: 12px; + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; +}`]})},rN=(e,t)=>t.id;function iN(e,t){if(e&1&&(F(0,`p`,4),K(1),I()),e&2){let e=W(2);D(),J(`The page has more @for blocks than the `,e.length,` listed.`)}}function aN(e,t){e&1&&(F(0,`span`,9),K(1,`@empty showing`),I())}function oN(e,t){e&1&&(F(0,`span`,10),K(1,`@empty`),I())}function sN(e,t){e&1&&(F(0,`span`,10),K(1,`renders nothing`),I())}function cN(e,t){if(e&1){let e=B();F(0,`li`)(1,`button`,6),H(`click`,function(){let t=S(e).$implicit;return C(W(3).showOwner.emit(t))})(`focus`,function(){let t=S(e).$implicit,n=W(3);return C(n.highlight.emit(n.target(t)))})(`blur`,function(){return S(e),C(W(3).highlight.emit(null))})(`mouseenter`,function(){let t=S(e).$implicit,n=W(3);return C(n.highlight.emit(n.target(t)))})(`mouseleave`,function(){return S(e),C(W(3).highlight.emit(null))}),F(2,`span`,7),K(3),I(),F(4,`span`,8),K(5),I(),A(6,aN,2,0,`span`,9)(7,oN,2,0,`span`,10)(8,sN,2,0,`span`,10),F(9,`span`,11),K(10),I()()()}if(e&2){let e=t.$implicit,n=W(3);D(),k(`aria-disabled`,e.owner?null:`true`),D(2),q(e.owner?.name??`Unknown component`),D(2),Y(` `,e.count,` `,e.count===1?`item`:`items`,` `),D(),j(e.showingEmpty?6:e.hasEmptyBlock?7:e.count===0?8:-1),D(4),q(n.trackLabel(e))}}function lN(e,t){if(e&1&&(F(0,`section`,1)(1,`h2`,2),K(2,` @for blocks `),F(3,`span`,3),K(4),I()(),A(5,iN,2,1,`p`,4),F(6,`ul`,5),M(7,cN,11,6,`li`,null,rN),I()()),e&2){let e=W(),t=W();D(4),q(t.truncated()?e.length+`+`:e.length),D(),j(t.truncated()?5:-1),D(2),N(e)}}function uN(e,t){e&1&&A(0,lN,9,2,`section`,1),e&2&&j(t.length?0:-1)}function dN(e,t){e&1&&L(0,`app-angular-unavailable`,0),e&2&&P(`page`,W().angular())}var fN={$index:`track $index`,item:`track item`,function:`track expression`},pN=class e{blocks=$(void 0);truncated=$(!1);supported=$(null);angular=$(null);showOwner=lv();highlight=lv();target(e){return e.rootIds[0]??e.owner?.id??null}trackLabel(e){return e.track?fN[e.track]:`track unknown`}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-for-blocks`]],inputs:{blocks:[1,`blocks`],truncated:[1,`truncated`],supported:[1,`supported`],angular:[1,`angular`]},outputs:{showOwner:`showOwner`,highlight:`highlight`},decls:2,vars:1,consts:[[`capability`,`forBlocks`,`missing`,``,`compact`,``,1,`for`,3,`page`],[`aria-labelledby`,`ct-for-title`,1,`for`],[`id`,`ct-for-title`,1,`for-title`],[1,`pill`],[1,`note`],[1,`for-list`],[`type`,`button`,1,`for-row`,3,`click`,`focus`,`blur`,`mouseenter`,`mouseleave`],[1,`name`,`mono`],[1,`count`],[1,`flag`,`empty`],[1,`flag`],[1,`track`,`mono`]],template:function(e,t){if(e&1&&A(0,uN,1,1)(1,dN,1,1,`app-angular-unavailable`,0),e&2){let e;j((e=t.blocks())?0:t.supported()===!1?1:-1,e)}},dependencies:[bD],styles:[`[_nghost-%COMP%] { + display: block; +} + +.for[_ngcontent-%COMP%] { + margin-top: 16px; +} + +.for-title[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + display: flex; + align-items: center; + gap: 6px; + margin: 0 0 8px; +} + +.pill[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 16px; + padding: 0 5px; + border-radius: 99px; + background: var(--%NS%surface-3); + color: var(--%NS%text-2); + font-size: 10px; + font-weight: 600; + letter-spacing: 0; +} + +.note[_ngcontent-%COMP%] { + margin: 0 0 8px; + color: var(--%NS%text-2); + font-size: 12px; + line-height: 1.5; +} + +.for-list[_ngcontent-%COMP%] { + display: grid; + gap: 6px; + margin: 0; + padding: 0; + list-style: none; +} + +.for-row[_ngcontent-%COMP%] { + justify-content: flex-start; + flex-wrap: wrap; + width: 100%; + height: auto; + min-height: var(--%NS%control-h); + padding: 6px 12px; + background: var(--%NS%surface); + border-color: var(--%NS%border); + text-align: left; +} + +.for-row[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.for-row[aria-disabled=true][_ngcontent-%COMP%] { + cursor: default; +} + +.name[_ngcontent-%COMP%] { + min-width: 0; + color: var(--%NS%text-strong); + font-size: 12px; + font-weight: 600; + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; +} + +.mono[_ngcontent-%COMP%] { + font-family: var(--%NS%font-mono); +} + +.count[_ngcontent-%COMP%] { + padding: 0 8px; + border-radius: 99px; + background: var(--%NS%surface-3); + color: var(--%NS%text-2); + font-size: 11px; + line-height: 18px; +} + +.flag[_ngcontent-%COMP%] { + padding: 0 6px; + border: 1px solid var(--%NS%border-strong); + border-radius: 5px; + color: var(--%NS%text-2); + font-family: var(--%NS%font-mono); + font-size: 10px; +} + +.flag.empty[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%warn) 30%, transparent); + background: color-mix(in srgb, var(--%NS%warn) 12%, transparent); + color: var(--%NS%warn-text); +} + +.track[_ngcontent-%COMP%] { + min-width: 0; + color: var(--%NS%text-2); + font-size: 12px; + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; +}`]})},mN=(e,t)=>t.id;function hN(e,t){if(e&1&&(F(0,`p`,11),K(1),I(),F(2,`h3`,7),K(3,`Still retained`),I(),F(4,`p`,6),K(5,` In a development build, Angular's DI debug data keeps every destroyed component in memory, so Pangular can't tell a leak from that. Check retainers in the Chrome DevTools Memory panel: take a heap snapshot after a few navigations, search for the class and look for a retainer other than Angular's DI debug records. Open subscriptions after destroy are still tracked below. `),I()),e&2){let e=W(),t=W();D(),q(t.plural(e.navigations,`navigation`))}}function gN(e,t){e&1&&(F(0,`p`,6),K(1,` This page cannot track destroyed components. It needs WeakRef, FinalizationRegistry and the Angular debug API of a development build. `),I())}function _N(e,t){if(e&1&&(F(0,`span`,18),K(1),I()),e&2){let e=W().$implicit,t=W(4);D(),q(t.plural(e.subscriptions,`open subscription`))}}function vN(e,t){if(e&1&&(F(0,`li`,13)(1,`span`,14),K(2),I(),F(3,`span`,15),K(4),I(),F(5,`span`,16),K(6),I(),F(7,`span`,17),K(8),I(),A(9,_N,2,1,`span`,18),I()),e&2){let e=t.$implicit,n=W(4);D(2),q(e.name),D(2),q(e.route||`/`),D(2),J(``,n.plural(e.navigations,`navigation`),` ago`),D(2),q(n.ageOf(e)),D(),j(e.subscriptions?9:-1)}}function yN(e,t){if(e&1&&(F(0,`p`,6),K(1),I(),F(2,`ul`,12),M(3,vN,10,5,`li`,13,mN),I()),e&2){let e=W(3);D(),Y(` Destroyed, yet still in memory after at least `,e.minNavigations,` navigations and `,e.minSeconds,` seconds. Take a heap snapshot, search for the class and read its retainers. `),D(2),N(e.retained().slice(0,e.shown))}}function bN(e,t){e&1&&(F(0,`p`,6),K(1,`No destroyed component is still in memory after a few navigations.`),I())}function xN(e,t){if(e&1&&(F(0,`p`,6),K(1),F(2,`span`,19),K(3),I(),K(4,`. Navigate a few times to see whether they stay. `),I()),e&2){let e=W(3);D(),J(` `,e.plural(e.recent().length,`instance`),` destroyed recently and not collected yet: `),D(2),q(e.names(e.recent()))}}function SN(e,t){if(e&1&&(A(0,gN,2,0,`p`,6),F(1,`p`,11),K(2),I(),F(3,`h3`,7),K(4,` Still retained `),F(5,`span`,8),K(6),I()(),A(7,yN,5,2)(8,bN,2,0,`p`,6),A(9,xN,5,2,`p`,6)),e&2){let e=W(),t=W();j(e.support.retention?-1:0),D(2),O_(` `,t.plural(e.navigations,`navigation`),` · `,t.retained().length,` still retained · `,t.plural(e.totals.collected,`instance`),` collected `),D(4),q(t.retained().length),D(),j(t.retained().length?7:8),D(2),j(t.recent().length?9:-1)}}function CN(e,t){if(e&1&&(F(0,`p`,6),K(1,` Off the page but not destroyed (detached views, such as a route reuse cache): `),F(2,`span`,19),K(3),I(),K(4,`. `),I()),e&2){let e=W(2);D(3),q(e.names(e.detached()))}}function wN(e,t){if(e&1&&(F(0,`span`,8),K(1),I()),e&2){let e=W();D(),q(e.subscriptions.length)}}function TN(e,t){if(e&1&&(F(0,`span`,15),K(1),I()),e&2){let e=W().$implicit;D(),q(e.source)}}function EN(e,t){if(e&1&&(F(0,`li`,13)(1,`span`,14),K(2),I(),F(3,`span`,16),K(4),I(),F(5,`span`,18),K(6),I(),A(7,TN,2,1,`span`,15),I()),e&2){let e=t.$implicit;D(2),q(e.name),D(2),J(``,e.count,` open`),D(2),J(` `,e.reason===`instance-destroyed`?`instance destroyed`:`no instance left`,` `),D(),j(e.source?7:-1)}}function DN(e,t){if(e&1&&(F(0,`p`,6),K(1,` Still open although the component that made them is gone. Tear them down with takeUntilDestroyed(), DestroyRef.onDestroy or the async pipe. `),I(),F(2,`ul`,12),M(3,EN,8,4,`li`,13,eg),I()),e&2){let e=W(2),t=W();D(3),N(e.subscriptions.slice(0,t.shown))}}function ON(e,t){e&1&&(F(0,`p`,6),K(1,`No subscription is still open after its component went away.`),I())}function kN(e,t){e&1&&(F(0,`p`,6),K(1,` Subscriptions are tied to component classes only; Angular 20 profiler hooks tie them to instances. `),I())}function AN(e,t){if(e&1&&(A(0,DN,5,0)(1,ON,2,0,`p`,6),A(2,kN,2,0,`p`,6)),e&2){let e=W();j(+!e.subscriptions.length),D(2),j(e.support.instances?-1:2)}}function jN(e,t){e&1&&(F(0,`p`,9),K(1,` Tracking could not start: the page has no RxJS the overlay can load. `),I())}function MN(e,t){e&1&&(F(0,`p`,6),K(1,` Off. Track subscriptions wraps Observable.prototype.subscribe on the page to record which component makes each subscription, until you stop it. Subscriptions made before you start are not seen, so reload or navigate again after starting. `),I())}function NN(e,t){e&1&&L(0,`app-angular-unavailable`,10),e&2&&P(`page`,W(2).angular())}function PN(e,t){if(e&1&&(F(0,`span`,16),K(1),I()),e&2){let e=W().$implicit,t=W(3);D(),q(t.plural(e.subscriptions,`subscription`))}}function FN(e,t){if(e&1){let e=B();F(0,`li`)(1,`button`,20),H(`click`,function(){let t=S(e).$implicit;return C(W(3).showComponent.emit(t.id))})(`focus`,function(){let t=S(e).$implicit;return C(W(3).highlight.emit(t.id))})(`blur`,function(){return S(e),C(W(3).highlight.emit(null))})(`mouseenter`,function(){let t=S(e).$implicit;return C(W(3).highlight.emit(t.id))})(`mouseleave`,function(){return S(e),C(W(3).highlight.emit(null))}),F(2,`span`,14),K(3),I(),F(4,`span`,16),K(5),I(),A(6,PN,2,1,`span`,16),I()()}if(e&2){let e=t.$implicit,n=W(2),r=W();D(3),q(e.name),D(2),q(r.effectsText(e)),D(),j(n.support.subscriptions===`on`?6:-1)}}function IN(e,t){e&1&&(F(0,`p`,6),K(1,` Angular files a component's effects under the template its host sits in, so effects of components side by side in one template show as shared. `),I())}function LN(e,t){if(e&1&&(F(0,`ul`,12),M(1,FN,7,3,`li`,null,mN),I(),A(3,IN,2,0,`p`,6)),e&2){let e=W(),t=W();D(),N(e.components.slice(0,t.shown)),D(2),j(e.components.some(t.hasShared)?3:-1)}}function RN(e,t){e&1&&(F(0,`p`,6),K(1,`No component on the page has a live effect or a tracked subscription.`),I())}function zN(e,t){if(e&1&&(A(0,hN,6,1)(1,SN,10,7),A(2,CN,5,1,`p`,6),F(3,`h3`,7),K(4,` Open subscriptions after destroy `),A(5,wN,2,1,`span`,8),I(),A(6,AN,3,2)(7,jN,2,0,`p`,9)(8,MN,2,0,`p`,6),F(9,`h3`,7),K(10,` Live effects and subscriptions `),F(11,`span`,8),K(12),I()(),A(13,NN,1,1,`app-angular-unavailable`,10),A(14,LN,4,1)(15,RN,2,0,`p`,6)),e&2){let e,n=t,r=W();j(n.support.retention===`dev-di-debug`?0:1),D(2),j(r.detached().length?2:-1),D(3),j(n.support.subscriptions===`on`?5:-1),D(),j((e=n.support.subscriptions)===`on`?6:e===`unavailable`?7:8),D(6),q(n.components.length),D(),j(n.support.effects?-1:13),D(),j(n.components.length?14:15)}}function BN(e,t){e&1&&(F(0,`p`,6),K(1,`No leak data from this page yet. It comes with the next report.`),I())}var VN=20;function HN(e){let t=Math.max(0,Math.round(e/1e3));if(t<120)return`${t} s`;let n=Math.round(t/60);return n<120?`${n} min`:`${Math.round(n/60)} h`}var UN=(e,t,n=`${t}s`)=>`${e} ${e===1?t:n}`,WN=class e{report=$(null);pageId=$(null);rpc=$(null);angular=$(null);now=$(()=>Date.now());showComponent=lv();highlight=lv();shown=VN;minNavigations=2;minSeconds=uw/1e3;plural=UN;busy=T(!1);announcement=T(``);tracking=X(()=>this.report()?.support.subscriptions===`on`);split=X(()=>{let e=this.report(),t=[],n=[],r=[];if(!e)return{retained:t,recent:n,detached:r};let i=e.support.retention===`dev-di-debug`;for(let a of e.destroyed)if(a.detached)r.push(a);else if(i)continue;else this.isRetained(e,a)?t.push(a):n.push(a);return{retained:t,recent:n,detached:r}});retained=X(()=>this.split().retained);recent=X(()=>this.split().recent);detached=X(()=>this.split().detached);ageMs(e,t){return Math.max(0,this.now()()-e.reportedAt+(e.at-t.destroyedAt))}isRetained(e,t){return t.navigations>=2&&this.ageMs(e,t)>=1e4}ageOf(e){let t=this.report();return t?`${HN(this.ageMs(t,e))} old`:``}names(e){let t=[...new Set(e.map(e=>e.name))];return t.length>6?`${t.slice(0,6).join(`, `)} and more`:t.join(`, `)}hasShared(e){return!!e.sharedEffects}effectsText(e){let t=UN(e.effects,`effect`);return e.sharedEffects?`${t}, ${e.sharedEffects} shared`:t}async toggle(){let e=this.pageId();if(!e||this.busy())return;let t=!this.tracking();this.busy.set(!0);try{await hT(this.rpc(),`request-leak-tracking`,{pageId:e,on:t}),this.announcement.set(t?`Tracking subscriptions. Reload or navigate to see subscriptions made at start.`:`Stopped tracking subscriptions.`)}catch{this.announcement.set(`Could not reach the page. Try again.`)}finally{this.busy.set(!1)}}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-leaks-section`]],inputs:{report:[1,`report`],pageId:[1,`pageId`],rpc:[1,`rpc`],angular:[1,`angular`],now:[1,`now`]},outputs:{showComponent:`showComponent`,highlight:`highlight`},decls:12,vars:5,consts:[[`aria-labelledby`,`ct-leaks-title`,1,`leaks`],[1,`head`],[`id`,`ct-leaks-title`],[`type`,`button`,1,`track`,3,`click`,`disabled`],[`role`,`status`,1,`sr-only`],[1,`hint`],[1,`note`],[1,`sub`],[1,`pill`],[`role`,`status`,1,`note`],[`capability`,`signalGraph`,`missing`,``,`compact`,``,3,`page`],[1,`stats`],[1,`rows`],[1,`row`],[1,`name`,`mono`],[1,`meta`,`mono`],[1,`count`],[1,`meta`],[1,`flag`,`warn`],[1,`mono`],[`type`,`button`,1,`row`,`live`,3,`click`,`focus`,`blur`,`mouseenter`,`mouseleave`]],template:function(e,t){if(e&1&&(F(0,`section`,0)(1,`div`,1)(2,`h2`,2),K(3,`Leaks`),I(),F(4,`button`,3),H(`click`,function(){return t.toggle()}),K(5,` Track subscriptions `),I(),F(6,`p`,4),K(7),I()(),F(8,`p`,5),K(9,` Components that left the page but are still in memory, and the live effects and RxJS subscriptions of each component. The browser collects garbage when it decides, so a retained instance is a hint: check what holds it in the Chrome DevTools Memory panel. `),I(),A(10,zN,16,7)(11,BN,2,0,`p`,6),I()),e&2){let e;D(4),P(`disabled`,!t.pageId()||!t.report()),k(`aria-pressed`,t.tracking())(`aria-disabled`,t.busy()||null),D(3),q(t.announcement()),D(3),j((e=t.report())?10:11,e)}},dependencies:[bD],styles:[`[_nghost-%COMP%] { + display: block; +} + +.leaks[_ngcontent-%COMP%] { + margin-top: 16px; +} + +.head[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; +} + +h2[_ngcontent-%COMP%] { + color: var(--%NS%text-3); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + margin: 0; +} + +.track[_ngcontent-%COMP%] { + height: var(--%NS%control-h); +} + +.track[aria-pressed=true][_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%accent) 30%, transparent); + background: color-mix(in srgb, var(--%NS%accent) 12%, transparent); + color: var(--%NS%accent-text); +} + +.track[aria-disabled=true][_ngcontent-%COMP%] { + cursor: progress; +} + +.track[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.hint[_ngcontent-%COMP%], +.note[_ngcontent-%COMP%], +.stats[_ngcontent-%COMP%] { + margin: 8px 0; + color: var(--%NS%text-2); + font-size: 12px; + line-height: 1.5; +} + +.stats[_ngcontent-%COMP%] { + color: var(--%NS%text); +} + +.sub[_ngcontent-%COMP%] { + display: flex; + align-items: center; + gap: 6px; + margin: 14px 0 6px; + color: var(--%NS%text-strong); + font-size: 12px; + font-weight: 600; +} + +.pill[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 16px; + padding: 0 5px; + border-radius: 99px; + background: var(--%NS%surface-3); + color: var(--%NS%text-2); + font-size: 10px; + font-weight: 600; +} + +.rows[_ngcontent-%COMP%] { + display: grid; + gap: 6px; + margin: 0; + padding: 0; + list-style: none; +} + +.row[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + min-height: var(--%NS%control-h); + padding: 6px 12px; + border: 1px solid var(--%NS%border); + border-radius: var(--%NS%radius-sm); + background: var(--%NS%surface); + font-size: 12px; +} + +.row.live[_ngcontent-%COMP%] { + justify-content: flex-start; + width: 100%; + height: auto; + text-align: left; +} + +.row.live[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: 2px; +} + +.name[_ngcontent-%COMP%] { + min-width: 0; + color: var(--%NS%text-strong); + font-weight: 600; + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; +} + +.mono[_ngcontent-%COMP%] { + font-family: var(--%NS%font-mono); +} + +.meta[_ngcontent-%COMP%] { + min-width: 0; + color: var(--%NS%text-2); + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; +} + +.count[_ngcontent-%COMP%] { + padding: 0 8px; + border-radius: 99px; + background: var(--%NS%surface-3); + color: var(--%NS%text-2); + font-size: 11px; + line-height: 18px; +} + +.flag[_ngcontent-%COMP%] { + padding: 0 6px; + border-radius: 5px; + font-size: 11px; + line-height: 18px; +} + +.flag.warn[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%warn) 30%, transparent); + background: color-mix(in srgb, var(--%NS%warn) 12%, transparent); + color: var(--%NS%warn-text); +}`]})},GN=12,KN=900;function qN(e){for(let t=e.parentElement;t;t=t.parentElement){let e=getComputedStyle(t);if(/(auto|scroll|overlay)/.test(e.overflowY))return t}return null}var JN=class{options;injector=x(bs);visibleTop=T(0);viewport=T(KN);rowHeight;host=null;scroller=null;detach=null;enabled;range;padTop;padBottom;constructor(e){this.options=e,this.rowHeight=T(e.estimate);let t=e.min??300;this.enabled=X(()=>e.count()>t),this.range=X(()=>{let t=e.count();if(!this.enabled())return{start:0,end:t};let n=this.rowHeight(),r=this.viewport(),i=Math.max(0,Math.min(this.visibleTop(),t*n-r)),a=Math.max(0,Math.min(t-1,Math.floor(i/n)-GN));return{start:a,end:Math.min(t,Math.max(a,Math.ceil((i+r)/n)+GN))}}),this.padTop=X(()=>this.enabled()?this.range().start*this.rowHeight():0),this.padBottom=X(()=>this.enabled()?(e.count()-this.range().end)*this.rowHeight():0),E(()=>{e.count(),cd(()=>this.update(),{injector:this.injector})}),x(Ss).onDestroy(()=>this.detach?.())}get count(){return this.options.count()}rendered(e){let{start:t,end:n}=this.range();return e>=t&&ethis.update();n.addEventListener(`scroll`,r,{passive:!0}),window.addEventListener(`resize`,r),this.detach=()=>{n.removeEventListener(`scroll`,r),window.removeEventListener(`resize`,r)},this.update()}update(){let e=this.host;if(e){if(this.scroller===e)this.place(e.scrollTop,e.clientHeight);else{let t=this.scroller?this.scroller.getBoundingClientRect().top:0,n=this.scroller?this.scroller.clientHeight:window.innerHeight;this.place(t-e.getBoundingClientRect().top,n)}this.options.fixed||this.measure()}}place(e,t){this.visibleTop.set(e),this.viewport.set(t>0?t:KN)}measure(){let e=this.host?.querySelectorAll(`[data-vindex]`);if(!e||e.length<2)return;let t=e[0].getBoundingClientRect(),n=(e[e.length-1].getBoundingClientRect().bottom-t.top)/e.length;n>4&&Math.abs(n-this.rowHeight())>1&&this.rowHeight.set(n)}reveal(e,t){if(this.enabled()&&!this.rendered(e)){let t=e*this.rowHeight(),n=this.viewport(),r=Math.max(0,t-n/2);if(this.place(r,n),this.host&&this.scroller===this.host)this.host.scrollTop=r;else if(this.host){let e=this.scroller?this.scroller.getBoundingClientRect().top:0,t=this.host.getBoundingClientRect().top-e+r;this.scroller?this.scroller.scrollTop+=t:window.scrollBy(0,t)}}t&&cd(t,{injector:this.injector})}};function YN(e){let t=e.ownerDocument.defaultView;if(!t)return null;for(let n=e.parentElement;n&&n!==e.ownerDocument.documentElement&&n!==e.ownerDocument.body;n=n.parentElement)if(/(auto|scroll|overlay)/.test(t.getComputedStyle(n).overflowY))return n;return null}var XN=e=>{let t=parseFloat(e);return Number.isFinite(t)?t:0};function ZN(e,t=8){let n=e.ownerDocument.defaultView;if(!n)return e.offsetHeight+t;let r=YN(e),i=r?n.getComputedStyle(r):null,a=i?XN(i.paddingTop):0,o=i?XN(i.scrollPaddingTop):0,s=XN(n.getComputedStyle(e).top);return Math.max(0,Math.ceil(a+s+e.offsetHeight+t-o))}function QN(e,t,n=8){let r=YN(e),i=t.getBoundingClientRect().bottom+n-e.getBoundingClientRect().top;i<=0||(r?r.scrollTop-=i:e.ownerDocument.defaultView?.scrollBy(0,-i))}var $N=[`input`];function eP(e,t){if(e&1){let e=B();R(0,`button`,9),U(`click`,function(){return S(e),C(W().save())}),w(),R(1,`svg`,10),fg(2,`path`,11),z(),fs(),R(3,`span`,12),K(4,`Save`),z()()}e&2&&k(`aria-label`,`Save filter `+W().query())}function tP(e,t){if(e&1){let e=B();R(0,`li`,14)(1,`button`,15),U(`click`,function(){let t=S(e).$implicit;return C(W(2).apply(t))}),K(2),z(),R(3,`button`,16),U(`click`,function(){let t=S(e).$implicit;return C(W(2).remove(t))}),w(),R(4,`svg`,10),fg(5,`path`,17),z()()()}if(e&2){let e=t.$implicit,n=W(2);G(`active`,e===n.query()),D(),k(`aria-pressed`,e===n.query())(`title`,e),D(),J(` `,e,` `),D(),k(`aria-label`,`Remove saved filter `+e)}}function nP(e,t){if(e&1&&(R(0,`ul`,7),M(1,tP,6,6,`li`,13,tg),z()),e&2){let e=W();D(),N(e.saved())}}function rP(e,t){if(e&1&&(R(0,`span`,8),K(1),z()),e&2){let e=W();D(),q(e.announcement())}}var iP=`pangular-pref:filters`,aP=200;function oP(e){try{let t=localStorage.getItem(`${iP}:${e}`),n=t?JSON.parse(t):[];if(!Array.isArray(n))return[];let r=n.filter(e=>typeof e==`string`&&!!e.trim()&&e.length<=aP);return[...new Set(r)].slice(-8)}catch{return[]}}function sP(e,t){try{t.length?localStorage.setItem(`${iP}:${e}`,JSON.stringify(t)):localStorage.removeItem(`${iP}:${e}`)}catch{}}var cP=class e{value=mv(``);label=$(`Filter`);placeholder=$(`Filter`);inputId=$(null);describedBy=$(null);saveKey=$(null);input=gv.required(`input`);host=x(Ml);injector=x(bs);announcement=T(``);query=X(()=>this.value().trim());saved=Q(()=>{let e=this.saveKey();return e?oP(e):[]});canSave=X(()=>{let e=this.query();return!!this.saveKey()&&!!e&&e.length<=aP&&!this.saved().includes(e)&&this.saved().length<8});focus(){this.input().nativeElement.focus()}clear(e){this.value()&&(e.preventDefault(),e.stopPropagation(),this.value.set(``))}save(){let e=this.saveKey(),t=this.query();if(!e||!this.canSave())return;let n=[...this.saved(),t];this.saved.set(n),sP(e,n),this.say(`Filter ${t} saved.`),this.focusAfterRender(`.chip:last-child .apply`)}apply(e){this.value.set(this.query()===e?``:e)}remove(e){let t=this.saveKey();if(!t)return;let n=this.saved().indexOf(e),r=this.saved().filter(t=>t!==e);if(this.saved.set(r),sP(t,r),this.say(`Saved filter ${e} removed.`),!r.length){this.focus();return}let i=Math.min(n,r.length-1)+1;this.focusAfterRender(`.chip:nth-child(${i}) .apply`)}focusAfterRender(e){cd(()=>{let t=this.host.nativeElement.querySelector(e);t?t.focus():this.focus()},{injector:this.injector})}say(e){this.announcement.update(t=>t===e?`${e} `:e)}static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-search-field`]],viewQuery:function(e,t){e&1&&Ng(t.input,$N,5),e&2&&Pg()},inputs:{value:[1,`value`],label:[1,`label`],placeholder:[1,`placeholder`],inputId:[1,`inputId`],describedBy:[1,`describedBy`],saveKey:[1,`saveKey`]},outputs:{value:`valueChange`},decls:9,vars:8,consts:[[`input`,``],[1,`box`],[`viewBox`,`0 0 24 24`,`aria-hidden`,`true`,1,`icon`],[`cx`,`11`,`cy`,`11`,`r`,`7`],[`d`,`m20 20-3.5-3.5`],[`type`,`search`,`autocomplete`,`off`,`spellcheck`,`false`,3,`input`,`keydown.escape`,`placeholder`,`value`],[`type`,`button`,1,`save`],[`aria-label`,`Saved filters`,1,`chips`],[`role`,`status`,1,`sr-only`],[`type`,`button`,1,`save`,3,`click`],[`viewBox`,`0 0 24 24`,`aria-hidden`,`true`],[`d`,`M6 4h12v16l-6-4-6 4z`],[1,`save-text`],[1,`chip`,3,`active`],[1,`chip`],[`type`,`button`,1,`apply`,3,`click`],[`type`,`button`,1,`remove`,3,`click`],[`d`,`M7 7l10 10M17 7 7 17`]],template:function(e,t){e&1&&(R(0,`div`,1),w(),R(1,`svg`,2),fg(2,`circle`,3)(3,`path`,4),z(),fs(),R(4,`input`,5,0),U(`input`,function(e){return t.value.set(e.target.value)})(`keydown.escape`,function(e){return t.clear(e)}),z()(),A(6,eP,5,1,`button`,6),A(7,nP,3,0,`ul`,7),A(8,rP,2,1,`span`,8)),e&2&&(D(4),V(`placeholder`,t.placeholder())(`value`,t.value()),k(`id`,t.inputId())(`aria-label`,t.label())(`aria-describedby`,t.describedBy()),D(2),j(t.canSave()?6:-1),D(),j(t.saveKey()&&t.saved().length?7:-1),D(),j(t.saveKey()?8:-1))},styles:[`[_nghost-%COMP%] { + display: flex; + flex: 1 1 220px; + flex-wrap: wrap; + align-items: center; + gap: 6px; + min-width: 0; +} + +.box[_ngcontent-%COMP%] { + position: relative; + flex: 1 1 180px; + min-width: 0; +} + +.icon[_ngcontent-%COMP%] { + position: absolute; + top: 50%; + left: 12px; + width: 14px; + height: 14px; + transform: translateY(-50%); + fill: none; + stroke: var(--%NS%text-3); + stroke-width: 2.2; + stroke-linecap: round; + pointer-events: none; +} + +input[_ngcontent-%COMP%] { + width: 100%; + height: var(--%NS%control-h); + padding: 0 12px 0 34px; + background: var(--%NS%bg); + border: 1px solid var(--%NS%control-border); + border-radius: var(--%NS%radius-sm); + color: var(--%NS%text); + font: inherit; + font-size: 13px; +} + +input[_ngcontent-%COMP%]::placeholder { + color: var(--%NS%text-3); +} + +input[_ngcontent-%COMP%]:focus { + outline: none; + border-color: var(--%NS%accent); + box-shadow: 0 0 0 3px var(--%NS%accent-soft); +} + +.save[_ngcontent-%COMP%] { + display: inline-flex; + flex: none; + align-items: center; + gap: 6px; + height: var(--%NS%control-h); + padding: 0 10px; + border: 1px solid var(--%NS%control-border); + border-radius: var(--%NS%radius-sm); + background: var(--%NS%surface); + color: var(--%NS%text); + font: inherit; + font-size: 12px; + cursor: pointer; + transition: background 150ms var(--%NS%ease); +} + +.save[_ngcontent-%COMP%]:hover { + background: var(--%NS%surface-2); +} + +.save[_ngcontent-%COMP%] svg[_ngcontent-%COMP%], +.remove[_ngcontent-%COMP%] svg[_ngcontent-%COMP%] { + width: 14px; + height: 14px; + fill: none; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; +} + +.chips[_ngcontent-%COMP%] { + display: flex; + flex-wrap: wrap; + gap: 6px; + min-width: 0; + margin: 0; + padding: 0; + list-style: none; +} + +.chip[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; + max-width: 100%; + height: 26px; + border: 1px solid var(--%NS%control-border); + border-radius: 999px; + background: var(--%NS%surface); + overflow: hidden; +} + +.chip.active[_ngcontent-%COMP%] { + border-color: color-mix(in srgb, var(--%NS%accent) 30%, transparent); + background: color-mix(in srgb, var(--%NS%accent) 12%, transparent); + color: var(--%NS%accent-text); +} + +.chip[_ngcontent-%COMP%] button[_ngcontent-%COMP%] { + height: 100%; + border: 0; + background: transparent; + color: inherit; + font: inherit; + cursor: pointer; +} + +.apply[_ngcontent-%COMP%] { + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 22ch; + padding: 0 4px 0 10px; + color: var(--%NS%text); + font-family: var(--%NS%font-mono); + font-size: 12px; +} + +.chip.active[_ngcontent-%COMP%] .apply[_ngcontent-%COMP%] { + color: var(--%NS%accent-text); +} + +.remove[_ngcontent-%COMP%] { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + padding: 0; + color: var(--%NS%text-2); +} + +.remove[_ngcontent-%COMP%]:hover { + color: var(--%NS%text-strong); +} + +.save[_ngcontent-%COMP%]:focus-visible, +.chip[_ngcontent-%COMP%] button[_ngcontent-%COMP%]:focus-visible { + outline: 2px solid var(--%NS%accent); + outline-offset: -2px; +} + +@media (max-width: 420px) { + .save-text[_ngcontent-%COMP%] { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + } +} +@media (prefers-reduced-motion: reduce) { + .save[_ngcontent-%COMP%] { + transition: none; + } +}`]})},lP=[`[redacted]`,`[email]`],uP=2e4;function dP(e){let t=0;for(let n of lP)for(let r=e.indexOf(n);r>=0;r=e.indexOf(n,r+n.length))t++;return t}function fP(e,t=uP){let n=0,r=t,i=new Set,a=[e];for(;a.length;){let e=a.pop();if(typeof e==`string`){if(--r<0)return{count:n,capped:!0};n+=dP(e);continue}if(typeof e==`object`&&e&&!i.has(e)){if(i.add(e),Array.isArray(e)){for(let t of e)a.push(t);continue}for(let[t,i]of Object.entries(e)){if(--r<0)return{count:n,capped:!0};n+=dP(t),a.push(i)}}}return{count:n,capped:!1}}function pP(e,t){if(e&1&&(R(0,`p`,0),K(1),R(2,`span`,1),K(3),z()()),e&2){let e=W();D(),O_(` `,e.result().capped?`At least `:``,``,e.result().count,` `,e.result().count===1?`value`:`values`,` masked `),D(2),q(e.why())}}var mP=class e{value=$(void 0);why=$(`by redaction (secret names, tokens and emails).`);result=X(()=>fP(this.value()));static ɵfac=function(t){return new(t||e)};static ɵcmp=O({type:e,selectors:[[`app-redaction-count`]],inputs:{value:[1,`value`],why:[1,`why`]},decls:1,vars:1,consts:[[`data-redaction-count`,``,1,`count`],[1,`why`]],template:function(e,t){e&1&&A(0,pP,4,4,`p`,0),e&2&&j(t.result().count>0?0:-1)},styles:[`[_nghost-%COMP%] { + display: block; +} + +.count[_ngcontent-%COMP%] { + margin: 0 0 8px; + color: var(--%NS%text-2); + font-size: 12px; + line-height: 1.5; + font-variant-numeric: tabular-nums; +} + +.why[_ngcontent-%COMP%] { + color: var(--%NS%text-2); +}`]})},hP=[`filterInput`],gP=[`treeEl`],_P=[`propFilterInput`],vP=()=>[],yP=e=>({$implicit:e}),bP=e=>({kind:`component`,id:e}),xP=(e,t)=>t.row.node.id,SP=(e,t)=>t.outlet+t.route,CP=(e,t)=>t+e;function wP(e,t){return this.sourceKey(t)}var TP=(e,t)=>t.formId,EP=(e,t)=>t.name+t.index,DP=(e,t)=>t.name,OP=(e,t)=>t.id,kP=(e,t)=>t.token+e;function AP(e,t){if(e&1){let e=B();F(0,`span`,38),K(1,`Page`),I(),F(2,`app-select`,39),H(`valueChange`,function(t){return S(e),C(W(2).selectPage(t))}),I()}if(e&2){let e=W(2);D(2),P(`options`,e.pageOptions())(`value`,e.page()?.pageId??null)}}function jP(e,t){if(e&1){let e=B();F(0,`button`,40),H(`click`,function(){return S(e),C(W(2).toggleDirectiveHosts())}),w(),F(1,`svg`,12),L(2,`path`,41)(3,`path`,42),I(),fs(),F(4,`span`,15),K(5,`Directive hosts`),I()()}e&2&&k(`aria-pressed`,W(2).showDirectiveHosts())}function MP(e,t){if(e&1&&(F(0,`p`,22),K(1),I()),e&2){let e=W(2);D(),q(e.pickMessage())}}function NP(e,t){if(e&1&&(F(0,`p`,23),K(1),I()),e&2){let e=W(2);D(),q(e.truncationText())}}function PP(e,t){e&1&&(F(0,`p`,44),K(1,`The filter only searches the components the tree lists.`),I())}function FP(e,t){e&1&&(F(0,`p`,44),K(1,`Directive hosts are hidden, so they are not searched.`),I())}function IP(e,t){if(e&1){let e=B();F(0,`div`,27)(1,`p`,43),K(2),I(),A(3,PP,2,0,`p`,44),A(4,FP,2,0,`p`,44),F(5,`button`,45),H(`click`,function(){return S(e),C(W(2).clearFilter())}),K(6,`Clear filters`),I()()}if(e&2){let e=W(2);D(2),J(`No components match “`,e.filter().trim(),`”`),D(),j(e.page().truncated?3:-1),D(),j(!e.showDirectiveHosts()&&e.page().directiveHostCount?4:-1)}}function LP(e,t){if(e&1&&(F(0,`p`,28),K(1),I()),e&2){let e=W(2);D(),J(` `,e.rows().length,` rows. Rows render as you scroll, so the browser's find only sees the ones near view; the filter above searches them all. `)}}function RP(e,t){if(e&1){let e=B();F(0,`span`,53),H(`click`,function(t){S(e),W();let n=L_(0);return C(W(2).toggle(n.node.id,t))}),w(),F(1,`svg`,54),L(2,`path`,55),I()()}e&2&&(W(),G(`open`,L_(0).expanded))}function zP(e,t){e&1&&L(0,`span`,48)}function BP(e,t){if(e&1&&(F(0,`span`,56),K(1),I(),F(2,`span`,57),K(3),I(),F(4,`span`,26),K(5,`directive host`),I()),e&2){W();let e=L_(0),t=W(2);D(),J(`<`,e.node.tag,`>`),D(),k(`title`,e.node.directives?.join(`, `)),D(),q(t.hostLabel(e.node))}}function VP(e,t){if(e&1&&(F(0,`span`,58),K(1),I(),F(2,`span`,59),K(3),I()),e&2){W();let e=L_(0);k(`title`,e.node.name),D(),q(e.node.name),D(),k(`title`,`<`+e.node.tag+`>`),D(),J(`<`,e.node.tag,`>`)}}function HP(e,t){if(e&1&&(F(0,`span`)(1,`span`,26),K(2,`Hydration: `),I(),K(3),I()),e&2){let e=t,n=W(3);r_(`hyd hyd-`+e),D(3),q(n.hydrationText(e))}}function UP(e,t){if(e&1&&(F(0,`span`,50),K(1),I()),e&2){let e=t.$implicit;D(),q(e.route||`/`)}}function WP(e,t){if(e&1&&(F(0,`span`,51),K(1),F(2,`span`,26),K(3),I()()),e&2){let e=t;D(),J(` `,e),D(2),J(` `,e===1?`check`:`checks`,` while recording`)}}function GP(e,t){if(e&1&&(F(0,`span`,52),K(1),F(2,`span`,26),K(3),I()()),e&2){W();let e=L_(0);k(`title`,e.node.directives.join(`, `)),D(),J(` +`,e.node.directives.length,` `),D(2),J(` `,e.node.directives.length===1?`directive`:`directives`,` `)}}function KP(e,t){if(e&1){let e=B();F_(0),F(1,`div`,46),H(`click`,function(){S(e);let t=L_(0);return C(W(2).select(t.node.id))})(`focus`,function(t){S(e);let n=L_(0),r=W(2);return r.focusId.set(n.node.id),r.highlight(n.node.id),C(r.showNameTip(t))})(`blur`,function(){S(e);let t=W(2);return t.highlight(null),C(t.nameTip.set(null))})(`keydown.escape`,function(){return S(e),C(W(2).nameTip.set(null))})(`mouseenter`,function(){S(e);let t=L_(0);return C(W(2).highlight(t.node.id))})(`mouseleave`,function(){return S(e),C(W(2).highlight(null))}),A(2,RP,3,2,`span`,47)(3,zP,1,0,`span`,48),A(4,BP,6,3)(5,VP,4,4),A(6,HP,4,3,`span`,49),M(7,UP,2,1,`span`,50,SP),A(9,WP,4,2,`span`,51),A(10,GP,4,3,`span`,52),I()}if(e&2){let e,n,r=t.$implicit,i=W(2),a=I_(r.row);D(),n_(`--%NS%depth`,a.depth),G(`selected`,i.selectedId()===a.node.id)(`dir-host`,a.node.kind===`directive`),k(`aria-level`,a.depth+1)(`aria-setsize`,a.setsize)(`aria-posinset`,a.posinset)(`aria-expanded`,a.hasChildren?a.expanded:null)(`aria-selected`,i.selectedId()===a.node.id)(`tabindex`,i.tabStop()===a.node.id?0:-1)(`data-vindex`,r.index)(`data-id`,a.node.id),D(),j(a.hasChildren?2:3),D(2),j(a.node.kind===`directive`?4:5),D(2),j((e=a.node.hydration)?6:-1,e),D(),N(i.routedById().get(a.node.id)??K_(20,vP)),D(2),j((n=i.cdHosts()[a.node.id])?9:-1,n),D(),j(a.node.kind!==`directive`&&a.node.directives?.length?10:-1)}}function qP(e,t){if(e&1&&(F(0,`div`,60),K(1),I()),e&2){let e=t;n_(`top`,e.top,`px`),D(),J(` `,e.text,` `)}}function JP(e,t){if(e&1&&(F(0,`li`,68),K(1),gg(2,71),I()),e&2){let e=t.$implicit;W(3);let n=Fg(3);D(),J(` `,e,` `),D(),P(`ngTemplateOutlet`,n)(`ngTemplateOutletContext`,q_(3,yP,e))}}function YP(e,t){if(e&1){let e=B();F(0,`div`,70)(1,`button`,45),H(`click`,function(){let t=S(e);return C(W(3).reveal(t.id,!0))}),K(2),I()()}e&2&&(D(2),J(` Show `,t.name,` `))}function XP(e,t){if(e&1&&(F(0,`section`,32)(1,`header`,61)(2,`span`,62),K(3,`directive host`),I(),F(4,`h2`,63),K(5),I(),F(6,`span`,64),K(7),I()(),F(8,`div`,65)(9,`h3`),K(10,` Directives `),F(11,`span`,66),K(12),I()(),F(13,`ul`,67),M(14,JP,3,5,`li`,68,CP),I()(),F(16,`div`,65)(17,`p`,69),K(18,` This element hosts directives but no component. Their inputs are bound in the template of the component that renders it. `),I(),A(19,YP,3,1,`div`,70),I()()),e&2){let e,n=t,r=W(2);D(5),J(`<`,n.tag,`>`),D(2),q(r.hostLabel(n)),D(5),q(n.directives?.length??0),D(2),N(n.directives??K_(4,vP)),D(5),j((e=r.ownerOf(n.id))?19:-1,e)}}function ZP(e,t){if(e&1&&(F(0,`p`,72),L(1,`app-source-link`,76),I()),e&2){let e=t;D(),P(`file`,e.file)(`line`,e.line)}}function QP(e,t){if(e&1&&(F(0,`p`,77)(1,`span`,79),K(2,`Likely`),I(),F(3,`span`,26),K(4,`, matched by class name: `),I(),L(5,`app-source-link`,76),I()),e&2){W();let e=L_(0);P(`title`,W(3).guessTitle),D(5),P(`file`,e[0].file)(`line`,e[0].line)}}function $P(e,t){if(e&1&&(F(0,`li`),L(1,`app-source-link`,76),I()),e&2){let e=t.$implicit;D(),P(`file`,e.file)(`line`,e.line)}}function eF(e,t){if(e&1&&(F(0,`div`,78)(1,`p`,80)(2,`span`,79),K(3,`Not certain`),I(),K(4),I(),F(5,`ul`,81),M(6,$P,2,2,`li`,null,wP,!0),I()()),e&2){W();let e=L_(0),t=W(),n=W(2);P(`title`,n.guessTitle),D(4),Y(` `,e.length,` source files define `,n.bareName(t.name),`: `),D(2),N(e)}}function tF(e,t){if(e&1&&(F_(0),A(1,QP,6,3,`p`,77)(2,eF,8,3,`div`,78)),e&2){let e=W(),t=I_(W(2).sourceCandidates(e));D(),j(t.length===1?1:t.length>1?2:-1)}}function nF(e,t){if(e&1){let e=B();F(0,`div`,82)(1,`button`,45),H(`click`,function(){S(e);let t=W();return C(W(2).revealInElements(t))}),K(2,`Reveal in Elements`),I(),F(3,`button`,45),H(`click`,function(){S(e);let t=W();return C(W(2).openSource(t))}),K(4,`Open source`),I()(),F(5,`p`,83),K(6),I()}if(e&2){let e=W(3);D(6),q(e.actionMessage())}}function rF(e,t){if(e&1){let e=B();F(0,`button`,85),H(`click`,function(){let t=S(e).$implicit;return C(W(5).showForm.emit(t.formId))}),K(1),I()}if(e&2){let e=t.$implicit;D(),J(` Show `,e.label,` in Forms `)}}function iF(e,t){if(e&1&&(F(0,`div`,73),M(1,rF,2,1,`button`,84,TP),I()),e&2){let e=W(),t=W(3);D(),N(t.formsIn(e.file))}}function aF(e,t){e&1&&A(0,iF,3,0,`div`,73),e&2&&j(W(3).formsIn(t.file).length?0:-1)}function oF(e,t){if(e&1){let e=B();F(0,`button`,45),H(`click`,function(){S(e);let t=W(2);return C(W(2).openInjector(t))}),K(1,` Show in Injectors `),F(2,`span`,26),K(3),I()()}if(e&2){let e=W(2);D(3),J(`: `,e.name)}}function sF(e,t){if(e&1){let e=B();F(0,`button`,45),H(`click`,function(){S(e);let t=W(2);return C(W(2).openSignals(t))}),K(1,` Show in Signals `),F(2,`span`,26),K(3),I()()}if(e&2){let e=W(2);D(3),J(`: `,e.name)}}function cF(e,t){if(e&1&&(F(0,`div`,73),A(1,oF,4,1,`button`,86),A(2,sF,4,1,`button`,86),I()),e&2){let e=W(3);D(),j(e.canShowInjector()?1:-1),D(),j(e.canShowSignals()?2:-1)}}function lF(e,t){if(e&1){let e=B();F(0,`div`,87)(1,`button`,45),H(`click`,function(){let t=S(e);return C(W(3).copyJson(t))}),K(2,`Copy as JSON`),I(),F(3,`button`,45),H(`click`,function(){let t=S(e);return C(W(3).copyHostPath(t))}),K(4,`Copy host path`),I()(),F(5,`p`,83),K(6),I()}if(e&2){let e=W(3);D(6),q(e.copyMessage())}}function uF(e,t){if(e&1&&L(0,`app-store-global`,74),e&2){let e=W(),t=W(2);P(`rpc`,t.rpc())(`pageId`,t.page()?.pageId)(`target`,q_(4,bP,e.id))(`label`,e.name)}}function dF(e,t){if(e&1&&(F(0,`dt`),K(1,`Routed`),I(),F(2,`dd`,89),K(3),I()),e&2){let e=t.$implicit;D(3),Y(``,e.route||`/`,` in outlet `,e.outlet)}}function fF(e,t){if(e&1&&(F(0,`span`,8),K(1),I()),e&2){let e=W(4);D(),Y(``,e.shownValues()?.count??0,` of `,e.valueTotal())}}function pF(e,t){if(e&1){let e=B();F(0,`div`,95)(1,`p`,43),K(2),I(),F(3,`p`,44),K(4,` The filter searches names, and the nested keys of values you opened. `),I(),F(5,`button`,45),H(`click`,function(){return S(e),C(W(5).clearPropFilter())}),K(6,`Clear filters`),I()()}if(e&2){let e=W(5);D(2),J(` No inputs, outputs or properties match “`,e.propFilter().trim(),`” `)}}function mF(e,t){if(e&1&&(F(0,`span`,100),K(1),I()),e&2){let e=W().$implicit;D(),J(`(`,e.prop,`)`)}}function hF(e,t){if(e&1&&(F(0,`li`,98)(1,`span`,99),K(2),A(3,mF,2,1,`span`,100),I(),L(4,`app-value-tree`,101),I()),e&2){let e=t.$implicit,n=W(4),r=W(3);G(`changed`,r.flashed(`input`,e.name)),D(2),J(` `,e.name,` `),D(),j(e.prop===e.name?-1:3),D(),P(`prop`,e)(`store`,r.values)(`query`,r.propFilter())(`componentId`,n.id)(`pageId`,r.page()?.pageId??null)(`rpc`,r.rpc())(`writes`,r.canEdit())}}function gF(e,t){if(e&1&&(F(0,`ul`,96),M(1,hF,5,11,`li`,97,DP),I()),e&2){let e=W(2);D(),N(e.inputs)}}function _F(e,t){e&1&&(F(0,`p`,69),K(1,`No inputs.`),I())}function vF(e,t){if(e&1&&(F(0,`div`,65)(1,`h3`),K(2,` Inputs `),F(3,`span`,66),K(4),I()(),A(5,gF,3,0,`ul`,96)(6,_F,2,0,`p`,69),I()),e&2){let e=W();D(4),q(e.inputs.length),D(),j(e.inputs.length?5:6)}}function yF(e,t){if(e&1&&(F(0,`li`,103)(1,`span`,89),K(2),I(),F(3,`span`,104),K(4),I()()),e&2){let e=t.$implicit;G(`on`,e.listened),D(2),q(e.name),D(2),q(e.listened?`listened`:`no listener`)}}function bF(e,t){if(e&1&&(F(0,`ul`,67),M(1,yF,5,4,`li`,102,DP),I()),e&2){let e=W(2);D(),N(e.outputs)}}function xF(e,t){e&1&&(F(0,`p`,69),K(1,`No outputs.`),I())}function SF(e,t){if(e&1&&(F(0,`div`,65)(1,`h3`),K(2,` Outputs `),F(3,`span`,66),K(4),I()(),A(5,bF,3,0,`ul`,67)(6,xF,2,0,`p`,69),I()),e&2){let e=W();D(4),q(e.outputs.length),D(),j(e.outputs.length?5:6)}}function CF(e,t){if(e&1&&(F(0,`span`,105),K(1),I()),e&2){let e=W().$implicit;D(),q(e.kind)}}function wF(e,t){if(e&1){let e=B();F(0,`button`,107),H(`click`,function(){S(e);let t=W().$implicit,n=W(5);return C(W(2).openSignals(n,t.prop))}),K(1,` Show in Signals`),F(2,`span`,26),K(3),I()()}if(e&2){let e=W().$implicit;D(3),J(`: `,e.name)}}function TF(e,t){if(e&1&&(F(0,`li`,98)(1,`span`,99),K(2),A(3,CF,2,1,`span`,105),A(4,wF,4,1,`button`,106),I(),L(5,`app-value-tree`,101),I()),e&2){let e=t.$implicit,n=W(4),r=W(3);G(`changed`,r.flashed(`property`,e.name)),D(2),J(` `,e.name,` `),D(),j(e.kind?3:-1),D(),j(e.kind===`signal`&&r.canShowSignals()&&!r.staticReport()?4:-1),D(),P(`prop`,e)(`store`,r.values)(`query`,r.propFilter())(`componentId`,n.id)(`pageId`,r.page()?.pageId??null)(`rpc`,r.rpc())(`writes`,r.canEdit())}}function EF(e,t){if(e&1&&(F(0,`ul`,96),M(1,TF,6,12,`li`,97,DP),I()),e&2){let e=W(2);D(),N(e.properties)}}function DF(e,t){e&1&&(F(0,`p`,69),K(1,`No other properties.`),I())}function OF(e,t){if(e&1&&(F(0,`div`,65)(1,`h3`),K(2,` Properties `),F(3,`span`,66),K(4),I()(),A(5,EF,3,0,`ul`,96)(6,DF,2,0,`p`,69),I()),e&2){let e=W();D(4),q(e.properties.length),D(),j(e.properties.length?5:6)}}function kF(e,t){if(e&1&&(F(0,`li`,68),K(1),I()),e&2){let e=t.$implicit;D(),q(e)}}function AF(e,t){if(e&1&&(F(0,`div`,65)(1,`h3`),K(2,` DOM listeners `),F(3,`span`,66),K(4),I()(),F(5,`ul`,67),M(6,kF,2,1,`li`,68,tg),I()()),e&2){let e=W(2);D(4),q(e.listeners.length),D(2),N(e.listeners)}}function jF(e,t){if(e&1&&(F(0,`li`,103)(1,`span`,89),K(2),I(),F(3,`span`,104),K(4),I()()),e&2){let e=t.$implicit;D(2),q(e.state),D(2),q(e.triggers.join(`, `)||`no triggers`)}}function MF(e,t){if(e&1&&(F(0,`div`,65)(1,`h3`),K(2,` Defer blocks `),F(3,`span`,66),K(4),I()(),F(5,`ul`,67),M(6,jF,5,2,`li`,103,OP),I()()),e&2){let e=W(5);D(4),q(e.ownedDeferBlocks().length),D(2),N(e.ownedDeferBlocks())}}function NF(e,t){if(e&1&&(F(0,`li`,98)(1,`span`,99),K(2),I(),L(3,`app-value-tree`,109),I()),e&2){let e=t.$implicit,n=W(2).$implicit,r=W(2),i=W(3);G(`changed`,i.flashed(`directive`,e.name,n.index)),D(2),q(e.name),D(),P(`prop`,e)(`store`,i.values)(`query`,i.propFilter())(`directive`,n.index)(`componentId`,r.id)(`pageId`,i.page()?.pageId??null)(`rpc`,i.rpc())}}function PF(e,t){if(e&1&&(F(0,`ul`,96),M(1,NF,4,10,`li`,97,DP),I()),e&2){let e=W().$implicit;D(),N(e.inputs)}}function FF(e,t){if(e&1&&(F(0,`li`,103)(1,`span`,89),K(2),I(),F(3,`span`,104),K(4),I()()),e&2){let e=t.$implicit;G(`on`,e.listened),D(2),q(e.name),D(2),q(e.listened?`listened`:`no listener`)}}function IF(e,t){if(e&1&&(F(0,`ul`,67),M(1,FF,5,4,`li`,102,DP),I()),e&2){let e=W().$implicit;D(),N(e.outputs)}}function LF(e,t){e&1&&(F(0,`p`,69),K(1,`No inputs or outputs.`),I())}function RF(e,t){if(e&1&&(F(0,`div`,65)(1,`h3`)(2,`span`,89),K(3),I(),F(4,`span`,105),K(5,`directive on host`),I()(),F(6,`p`,108),gg(7,71),I(),A(8,PF,3,0,`ul`,96),A(9,IF,3,0,`ul`,67),A(10,LF,2,0,`p`,69),I()),e&2){let e=t.$implicit;W(5);let n=Fg(3);D(3),q(e.name),D(4),P(`ngTemplateOutlet`,n)(`ngTemplateOutletContext`,q_(6,yP,e.name)),D(),j(e.inputs.length?8:-1),D(),j(e.outputs.length?9:-1),D(),j(!e.inputs.length&&!e.outputs.length?10:-1)}}function zF(e,t){if(e&1&&(L(0,`app-redaction-count`,94),A(1,pF,7,1,`div`,95),A(2,vF,7,2,`div`,65),A(3,SF,7,2,`div`,65),A(4,OF,7,2,`div`,65),A(5,AF,8,1,`div`,65),A(6,MF,8,1,`div`,65),M(7,RF,11,8,`div`,65,EP)),e&2){let e=t,n=W(),r=W(3);P(`value`,e),D(),j(r.propQuery()&&!e.count?1:-1),D(),j(!r.propQuery()||e.inputs.length?2:-1),D(),j(!r.propQuery()||e.outputs.length?3:-1),D(),j(!r.propQuery()||e.properties.length?4:-1),D(),j(!r.propQuery()&&n.listeners.length?5:-1),D(),j(!r.propQuery()&&r.ownedDeferBlocks().length?6:-1),D(),N(e.directives)}}function BF(e,t){if(e&1&&(F(0,`span`,105),K(1),I()),e&2){let e=t.$implicit;D(),q(e)}}function VF(e,t){if(e&1&&(F(0,`span`,112),K(1,`from `),F(2,`span`,89),K(3),I()()),e&2){let e=W().$implicit;D(3),q(e.providedByName??`an injector`)}}function HF(e,t){e&1&&(F(0,`span`,113),K(1,`not provided`),I())}function UF(e,t){if(e&1&&(F(0,`li`,110)(1,`span`,111),K(2),I(),M(3,BF,2,1,`span`,105,tg),A(5,VF,4,1,`span`,112)(6,HF,2,0,`span`,113),I()),e&2){let e=t.$implicit;D(2),q(e.token),D(),N(e.flags),D(2),j(e.providedBy?5:6)}}function WF(e,t){if(e&1&&(F(0,`ul`,92),M(1,UF,7,2,`li`,110,kP),I()),e&2){let e=W();D(),N(e.dependencies)}}function GF(e,t){e&1&&(F(0,`p`,69),K(1,`Nothing is injected through the constructor or inject().`),I())}function KF(e,t){if(e&1){let e=B();F(0,`app-component-pipes`,114),H(`showPipe`,function(t){return S(e),C(W(4).showPipe.emit(t))}),I()}if(e&2){let e=W(2),t=W(2);P(`rpc`,t.rpc())(`pageId`,t.page()?.pageId)(`component`,t.bareName(e.name))(`canShowPipes`,!t.staticReport())}}function qF(e,t){if(e&1){let e=B();F(0,`dl`,88)(1,`dt`),K(2,`Change detection`),I(),F(3,`dd`),K(4),I(),F(5,`dt`),K(6,`Encapsulation`),I(),F(7,`dd`),K(8),I(),F(9,`dt`),K(10,`Host path`),I(),F(11,`dd`,89),K(12),I(),M(13,dF,4,2,null,null,SP),I(),F(15,`div`,90)(16,`app-search-field`,91,3),N_(`valueChange`,function(t){S(e);let n=W(3);return M_(n.propFilter,t)||(n.propFilter=t),C(t)}),I(),A(18,fF,2,2,`span`,8),F(19,`p`,21),K(20),I()(),A(21,zF,9,7),F(22,`div`,65)(23,`h3`),K(24,` Injected `),F(25,`span`,66),K(26),I()(),A(27,WF,3,0,`ul`,92)(28,GF,2,0,`p`,69),I(),A(29,KF,1,4,`app-component-pipes`,93)}if(e&2){let e,n=t,r=W(),i=W(2);D(4),q(n.changeDetection??`Unknown`),D(4),q(n.encapsulation??`Unknown`),D(4),q(n.path),D(),N(i.routedById().get(r.id)??K_(10,vP)),D(3),j_(`value`,i.propFilter),D(2),j(i.propQuery()?18:-1),D(2),q(i.propFilterStatus()),D(),j((e=i.shownValues())?21:-1,e),D(5),q(n.dependencies.length),D(),j(n.dependencies.length?27:28),D(2),j(i.canShowPipes()?29:-1)}}function JF(e,t){e&1&&(F(0,`div`,75),L(1,`span`,115),F(2,`p`,44),K(3,`Reading live values from the page…`),I()())}function YF(e,t){if(e&1&&(F(0,`section`,32)(1,`header`,61)(2,`span`,62),K(3,`component`),I(),F(4,`h2`,63),K(5),I(),F(6,`span`,64),K(7),I(),A(8,ZP,2,2,`p`,72)(9,tF,3,2),A(10,nF,7,1),A(11,aF,1,1),A(12,cF,3,2,`div`,73),A(13,lF,7,1),A(14,uF,1,6,`app-store-global`,74),I(),A(15,qF,30,11)(16,JF,4,0,`div`,75),I()),e&2){let e,n,r,i,a=t,o=W(2);D(5),q(a.name),D(2),J(`<`,a.tag,`>`),D(),j((e=o.detail()?.source)?8:9,e),D(2),j(o.devtoolsActions()?10:-1),D(),j((n=o.sourceFor(a))?11:-1,n),D(),j((o.canShowInjector()||o.canShowSignals())&&!o.staticReport()?12:-1),D(),j((r=o.detail())?13:-1,r),D(),j(!o.staticReport()&&!o.native()?14:-1),D(),j((i=o.detail())?15:16,i)}}function XF(e,t){e&1&&(F(0,`p`,43),K(1,`The selected component was destroyed`),I(),F(2,`p`,44)(3,`span`,89),K(4),I(),K(5,` is no longer on the page. Select another component to see its live values. `),I()),e&2&&(D(4),q(t))}function ZF(e,t){e&1&&(F(0,`p`,44),K(1,` Select a component to see its live inputs, properties, outputs, change detection and injected services. `),I())}function QF(e,t){if(e&1&&(F(0,`div`,33),A(1,XF,6,1)(2,ZF,2,0,`p`,44),I()),e&2){let e,t=W(2);D(),j((e=t.destroyed())?1:2,e)}}function $F(e,t){e&1&&L(0,`app-angular-unavailable`,35),e&2&&P(`page`,W(2).angularPage())}function eI(e,t){if(e&1){let e=B();F(0,`app-hydration-section`,116),H(`overlay`,function(t){return S(e),C(W(2).setHydrationOverlay(t))})(`showNode`,function(t){return S(e),C(W(2).revealNode(t))})(`highlight`,function(t){return S(e),C(W(2).highlight(t))}),I()}e&2&&P(`report`,W(2).page()?.hydration)}function tI(e,t){if(e&1){let e=B();F(0,`app-cd-recording`,117),H(`showComponent`,function(t){return S(e),C(W(2).showCdComponent(t))}),I(),F(1,`app-leaks-section`,118),H(`showComponent`,function(t){return S(e),C(W(2).showLeakComponent(t))})(`highlight`,function(t){return S(e),C(W(2).highlight(t))}),I()}if(e&2){let e=W(2);P(`page`,e.cdPage())(`pageId`,e.page().pageId)(`rpc`,e.rpc())(`native`,e.native()),D(),P(`report`,e.leaksPage())(`pageId`,e.page().pageId)(`rpc`,e.rpc())(`angular`,e.angularPage())}}function nI(e,t){if(e&1){let e=B();F(0,`p`,4),K(1,` Every component instance on the page, in the order Angular rendered them. Hover a row to highlight its host in the page, and select it to read its live inputs, properties, outputs and injected services. `),I(),F(2,`div`,5)(3,`app-search-field`,6,1),H(`valueChange`,function(t){return S(e),C(W().setFilter(t))}),I(),F(5,`span`,7),K(6,` Type plain text, a tag such as