From 02a326d5ac8040c447e43b9183b873f16fd94ed5 Mon Sep 17 00:00:00 2001 From: Paul Basanets Date: Sat, 30 May 2026 00:33:08 +0300 Subject: [PATCH 1/8] feat(dashboard): rewrite in Svelte 5 + TypeScript Replace the legacy jQuery dashboard with a Svelte 5 + TypeScript app. - New Vite-based build pipeline + CI; remove the legacy jQuery dashboard. - Banners, "What's New", and config-modal wiring; restore legacy visual parity. - Shared modal-action / Confirm primitives, token-driven styles, a11y and a header menu. - Test scaffolding (shared fetch/fixture helpers) and broadened store/component coverage. - Single-pass highlightTools and escapeHtml hardening. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/dashboard.yml | 38 + .gitignore | 2 + .serena/memories/tech_stack.md | 1 + .serena/project.yml | 29 +- dashboard/.gitignore | 4 + dashboard/.prettierignore | 3 + dashboard/.prettierrc | 1 + dashboard/CLAUDE.md | 120 + dashboard/README.md | 89 + dashboard/eslint.config.js | 31 + dashboard/index.html | 21 + dashboard/package-lock.json | 5504 +++++++++++++++++ dashboard/package.json | 37 + dashboard/scripts/clean-assets.mjs | 4 + dashboard/src/App.svelte | 87 + .../components/banners/BannerCarousel.svelte | 65 + dashboard/src/components/common/Button.svelte | 49 + dashboard/src/components/common/Card.svelte | 28 + .../src/components/common/Collapsible.svelte | 73 + .../src/components/common/Combobox.svelte | 126 + dashboard/src/components/common/Modal.svelte | 121 + .../src/components/common/Spinner.svelte | 19 + .../src/components/logs/LogToolbar.svelte | 61 + .../src/components/logs/LogViewer.svelte | 56 + dashboard/src/components/logs/LogsPage.svelte | 16 + .../components/modals/AddLanguageModal.svelte | 40 + .../modals/CancelExecutionModal.svelte | 17 + .../src/components/modals/ConfirmModal.svelte | 34 + .../modals/CreateMemoryModal.svelte | 44 + .../modals/DeleteMemoryModal.svelte | 10 + .../components/modals/EditMemoryModal.svelte | 116 + .../modals/EditSerenaConfigModal.svelte | 40 + .../src/components/modals/ModalHost.svelte | 41 + .../modals/RemoveLanguageModal.svelte | 10 + .../components/modals/ShutdownModal.svelte | 22 + .../overview/CancelledExecutions.svelte | 44 + .../src/components/overview/ConfigCard.svelte | 254 + .../overview/ExecutionsQueue.svelte | 67 + .../components/overview/LastExecution.svelte | 94 + .../src/components/overview/ListPanel.svelte | 48 + .../components/overview/NewsSection.svelte | 49 + .../components/overview/OverviewPage.svelte | 104 + .../components/overview/ProjectsPanel.svelte | 68 + .../components/overview/ToolUsageBars.svelte | 53 + dashboard/src/components/shell/Header.svelte | 118 + .../src/components/shell/ThemeToggle.svelte | 22 + .../src/components/stats/ChartPanel.svelte | 118 + .../src/components/stats/StatsPage.svelte | 75 + .../src/components/stats/StatsSummary.svelte | 35 + dashboard/src/lib/api/client.ts | 31 + dashboard/src/lib/api/endpoints.ts | 52 + dashboard/src/lib/api/mutation.ts | 23 + dashboard/src/lib/api/types.ts | 126 + dashboard/src/lib/banners.ts | 50 + dashboard/src/lib/charts.ts | 30 + dashboard/src/lib/confirmDiscard.ts | 4 + dashboard/src/lib/format.ts | 42 + dashboard/src/lib/modalAction.svelte.ts | 37 + dashboard/src/lib/news.ts | 4 + dashboard/src/lib/pollers.ts | 14 + dashboard/src/lib/polling.ts | 36 + dashboard/src/lib/stores/config.svelte.ts | 23 + dashboard/src/lib/stores/executions.svelte.ts | 65 + dashboard/src/lib/stores/logs.svelte.ts | 29 + dashboard/src/lib/stores/modal.svelte.ts | 28 + dashboard/src/lib/stores/stats.svelte.ts | 26 + dashboard/src/lib/stores/theme.svelte.ts | 29 + dashboard/src/lib/title.ts | 3 + dashboard/src/lib/validation.ts | 3 + dashboard/src/main.ts | 7 + dashboard/src/styles/global.css | 85 + dashboard/src/styles/tokens.css | 66 + dashboard/src/types/frappe-charts.d.ts | 7 + dashboard/svelte.config.js | 6 + dashboard/tests/add-language-modal.test.ts | 21 + dashboard/tests/banners.test.ts | 34 + dashboard/tests/charts.test.ts | 23 + dashboard/tests/client.test.ts | 36 + dashboard/tests/combobox.test.ts | 14 + dashboard/tests/config-card.test.ts | 52 + dashboard/tests/config-store.test.ts | 29 + dashboard/tests/confirmDiscard.test.ts | 19 + dashboard/tests/create-memory-modal.test.ts | 27 + dashboard/tests/delete-memory-modal.test.ts | 15 + dashboard/tests/edit-memory-modal.test.ts | 24 + .../tests/edit-serena-config-modal.test.ts | 23 + dashboard/tests/endpoints.test.ts | 29 + dashboard/tests/executions-queue.test.ts | 48 + dashboard/tests/executions-store.test.ts | 66 + dashboard/tests/format.test.ts | 39 + dashboard/tests/helpers.ts | 37 + dashboard/tests/last-execution.test.ts | 33 + dashboard/tests/log-viewer.test.ts | 14 + dashboard/tests/logs-store.test.ts | 23 + dashboard/tests/modal-action.test.ts | 22 + dashboard/tests/modal-store.test.ts | 13 + dashboard/tests/mutation.test.ts | 28 + dashboard/tests/news.test.ts | 9 + dashboard/tests/pollers.test.ts | 14 + dashboard/tests/polling.test.ts | 42 + dashboard/tests/setup.ts | 32 + dashboard/tests/stats-store.test.ts | 25 + dashboard/tests/stats-summary.test.ts | 18 + dashboard/tests/theme.test.ts | 19 + dashboard/tests/title.test.ts | 12 + dashboard/tests/tool-usage-bars.test.ts | 13 + dashboard/tests/validation.test.ts | 14 + dashboard/tsconfig.json | 18 + dashboard/vite.config.ts | 58 + docs/02-usage/060_dashboard.md | 2 +- .../2026-05-27-dashboard-parity-fixes.md | 1937 ++++++ .../2026-05-27-dashboard-review-fixes.md | 1856 ++++++ .../plans/2026-05-27-dashboard-v2.md | 2785 +++++++++ ...026-05-27-dashboard-parity-fixes-design.md | 198 + ...026-05-27-dashboard-review-fixes-design.md | 156 + .../specs/2026-05-27-dashboard-v2-design.md | 274 + pyproject.toml | 2 + src/serena/agent.py | 2 +- .../dashboard/assets/index-DK0wQuCD.css | 1 + .../dashboard/assets/index-Jw8r4LTr.js | 26 + src/serena/resources/dashboard/dashboard.css | 1681 ----- src/serena/resources/dashboard/dashboard.js | 2386 ------- src/serena/resources/dashboard/index.html | 393 +- src/serena/resources/dashboard/jquery.min.js | 2 - 124 files changed, 17120 insertions(+), 4458 deletions(-) create mode 100644 .github/workflows/dashboard.yml create mode 100644 dashboard/.gitignore create mode 100644 dashboard/.prettierignore create mode 100644 dashboard/.prettierrc create mode 100644 dashboard/CLAUDE.md create mode 100644 dashboard/README.md create mode 100644 dashboard/eslint.config.js create mode 100644 dashboard/index.html create mode 100644 dashboard/package-lock.json create mode 100644 dashboard/package.json create mode 100644 dashboard/scripts/clean-assets.mjs create mode 100644 dashboard/src/App.svelte create mode 100644 dashboard/src/components/banners/BannerCarousel.svelte create mode 100644 dashboard/src/components/common/Button.svelte create mode 100644 dashboard/src/components/common/Card.svelte create mode 100644 dashboard/src/components/common/Collapsible.svelte create mode 100644 dashboard/src/components/common/Combobox.svelte create mode 100644 dashboard/src/components/common/Modal.svelte create mode 100644 dashboard/src/components/common/Spinner.svelte create mode 100644 dashboard/src/components/logs/LogToolbar.svelte create mode 100644 dashboard/src/components/logs/LogViewer.svelte create mode 100644 dashboard/src/components/logs/LogsPage.svelte create mode 100644 dashboard/src/components/modals/AddLanguageModal.svelte create mode 100644 dashboard/src/components/modals/CancelExecutionModal.svelte create mode 100644 dashboard/src/components/modals/ConfirmModal.svelte create mode 100644 dashboard/src/components/modals/CreateMemoryModal.svelte create mode 100644 dashboard/src/components/modals/DeleteMemoryModal.svelte create mode 100644 dashboard/src/components/modals/EditMemoryModal.svelte create mode 100644 dashboard/src/components/modals/EditSerenaConfigModal.svelte create mode 100644 dashboard/src/components/modals/ModalHost.svelte create mode 100644 dashboard/src/components/modals/RemoveLanguageModal.svelte create mode 100644 dashboard/src/components/modals/ShutdownModal.svelte create mode 100644 dashboard/src/components/overview/CancelledExecutions.svelte create mode 100644 dashboard/src/components/overview/ConfigCard.svelte create mode 100644 dashboard/src/components/overview/ExecutionsQueue.svelte create mode 100644 dashboard/src/components/overview/LastExecution.svelte create mode 100644 dashboard/src/components/overview/ListPanel.svelte create mode 100644 dashboard/src/components/overview/NewsSection.svelte create mode 100644 dashboard/src/components/overview/OverviewPage.svelte create mode 100644 dashboard/src/components/overview/ProjectsPanel.svelte create mode 100644 dashboard/src/components/overview/ToolUsageBars.svelte create mode 100644 dashboard/src/components/shell/Header.svelte create mode 100644 dashboard/src/components/shell/ThemeToggle.svelte create mode 100644 dashboard/src/components/stats/ChartPanel.svelte create mode 100644 dashboard/src/components/stats/StatsPage.svelte create mode 100644 dashboard/src/components/stats/StatsSummary.svelte create mode 100644 dashboard/src/lib/api/client.ts create mode 100644 dashboard/src/lib/api/endpoints.ts create mode 100644 dashboard/src/lib/api/mutation.ts create mode 100644 dashboard/src/lib/api/types.ts create mode 100644 dashboard/src/lib/banners.ts create mode 100644 dashboard/src/lib/charts.ts create mode 100644 dashboard/src/lib/confirmDiscard.ts create mode 100644 dashboard/src/lib/format.ts create mode 100644 dashboard/src/lib/modalAction.svelte.ts create mode 100644 dashboard/src/lib/news.ts create mode 100644 dashboard/src/lib/pollers.ts create mode 100644 dashboard/src/lib/polling.ts create mode 100644 dashboard/src/lib/stores/config.svelte.ts create mode 100644 dashboard/src/lib/stores/executions.svelte.ts create mode 100644 dashboard/src/lib/stores/logs.svelte.ts create mode 100644 dashboard/src/lib/stores/modal.svelte.ts create mode 100644 dashboard/src/lib/stores/stats.svelte.ts create mode 100644 dashboard/src/lib/stores/theme.svelte.ts create mode 100644 dashboard/src/lib/title.ts create mode 100644 dashboard/src/lib/validation.ts create mode 100644 dashboard/src/main.ts create mode 100644 dashboard/src/styles/global.css create mode 100644 dashboard/src/styles/tokens.css create mode 100644 dashboard/src/types/frappe-charts.d.ts create mode 100644 dashboard/svelte.config.js create mode 100644 dashboard/tests/add-language-modal.test.ts create mode 100644 dashboard/tests/banners.test.ts create mode 100644 dashboard/tests/charts.test.ts create mode 100644 dashboard/tests/client.test.ts create mode 100644 dashboard/tests/combobox.test.ts create mode 100644 dashboard/tests/config-card.test.ts create mode 100644 dashboard/tests/config-store.test.ts create mode 100644 dashboard/tests/confirmDiscard.test.ts create mode 100644 dashboard/tests/create-memory-modal.test.ts create mode 100644 dashboard/tests/delete-memory-modal.test.ts create mode 100644 dashboard/tests/edit-memory-modal.test.ts create mode 100644 dashboard/tests/edit-serena-config-modal.test.ts create mode 100644 dashboard/tests/endpoints.test.ts create mode 100644 dashboard/tests/executions-queue.test.ts create mode 100644 dashboard/tests/executions-store.test.ts create mode 100644 dashboard/tests/format.test.ts create mode 100644 dashboard/tests/helpers.ts create mode 100644 dashboard/tests/last-execution.test.ts create mode 100644 dashboard/tests/log-viewer.test.ts create mode 100644 dashboard/tests/logs-store.test.ts create mode 100644 dashboard/tests/modal-action.test.ts create mode 100644 dashboard/tests/modal-store.test.ts create mode 100644 dashboard/tests/mutation.test.ts create mode 100644 dashboard/tests/news.test.ts create mode 100644 dashboard/tests/pollers.test.ts create mode 100644 dashboard/tests/polling.test.ts create mode 100644 dashboard/tests/setup.ts create mode 100644 dashboard/tests/stats-store.test.ts create mode 100644 dashboard/tests/stats-summary.test.ts create mode 100644 dashboard/tests/theme.test.ts create mode 100644 dashboard/tests/title.test.ts create mode 100644 dashboard/tests/tool-usage-bars.test.ts create mode 100644 dashboard/tests/validation.test.ts create mode 100644 dashboard/tsconfig.json create mode 100644 dashboard/vite.config.ts create mode 100644 docs/superpowers/plans/2026-05-27-dashboard-parity-fixes.md create mode 100644 docs/superpowers/plans/2026-05-27-dashboard-review-fixes.md create mode 100644 docs/superpowers/plans/2026-05-27-dashboard-v2.md create mode 100644 docs/superpowers/specs/2026-05-27-dashboard-parity-fixes-design.md create mode 100644 docs/superpowers/specs/2026-05-27-dashboard-review-fixes-design.md create mode 100644 docs/superpowers/specs/2026-05-27-dashboard-v2-design.md create mode 100644 src/serena/resources/dashboard/assets/index-DK0wQuCD.css create mode 100644 src/serena/resources/dashboard/assets/index-Jw8r4LTr.js delete mode 100644 src/serena/resources/dashboard/dashboard.css delete mode 100644 src/serena/resources/dashboard/dashboard.js delete mode 100644 src/serena/resources/dashboard/jquery.min.js diff --git a/.github/workflows/dashboard.yml b/.github/workflows/dashboard.yml new file mode 100644 index 000000000..8fbae4038 --- /dev/null +++ b/.github/workflows/dashboard.yml @@ -0,0 +1,38 @@ +name: Dashboard + +on: + pull_request: + paths: + - 'dashboard/**' + - 'src/serena/resources/dashboard/**' + push: + branches: + - main + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: dashboard + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: dashboard/package-lock.json + - run: npm ci + - run: npm run check + - run: npm test + - run: npm run lint + - run: npm run build + - name: Fail if committed build output is stale + run: git diff --exit-code -- ../src/serena/resources/dashboard diff --git a/.gitignore b/.gitignore index 9d8204679..a32e5cc52 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,8 @@ lib/ !test/resources/repos/dart/test_repo/lib/diagnostics_sample.dart !test/resources/repos/svelte/test_repo/src/lib/ !test/resources/repos/svelte/test_repo/src/lib/** +!dashboard/src/lib/ +!dashboard/src/lib/** lib64/ parts/ sdist/ diff --git a/.serena/memories/tech_stack.md b/.serena/memories/tech_stack.md index f868cca61..1fa081f03 100644 --- a/.serena/memories/tech_stack.md +++ b/.serena/memories/tech_stack.md @@ -8,3 +8,4 @@ - Dev deps: `ruff` (lint+format), `mypy` (strict), `pytest` (+ `pytest-xdist`, `pytest-timeout`, `syrupy` snapshots), `sphinx`/`jupyter-book` for docs. - Optional extras: `agno` (Agno agent integration), `google` (gemini). - LSP client core lives under `src/solidlsp/`; one subdir per supported language server under `language_servers/`. +- Dashboard frontend: a separate **Svelte 5 + TypeScript + Vite** project under `dashboard/` (its own npm world). Its build output is committed to `src/serena/resources/dashboard/` and shipped in the wheel; Python-only contributors need no Node. diff --git a/.serena/project.yml b/.serena/project.yml index 8a180f591..76168a64a 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -3,24 +3,27 @@ project_name: "serena" # list of languages for which language servers are started; choose from: -# al ansible bash clojure cpp -# cpp_ccls crystal csharp csharp_omnisharp dart -# elixir elm erlang fortran fsharp -# go groovy haskell haxe hlsl -# java json julia kotlin lean4 -# lua luau markdown matlab msl -# nix ocaml pascal perl php -# php_phpactor powershell python python_jedi python_ty -# r rego ruby ruby_solargraph rust -# scala solidity swift systemverilog terraform -# toml typescript typescript_vts vue yaml -# zig +# al angular ansible bash clojure +# cpp cpp_ccls crystal csharp csharp_omnisharp +# dart elixir elm erlang fortran +# fsharp go groovy haskell haxe +# hlsl html java json julia +# kotlin lean4 lua luau markdown +# matlab msl nix ocaml pascal +# perl php php_phpactor powershell python +# python_jedi python_ty r rego ruby +# ruby_solargraph rust scala scss solidity +# svelte swift systemverilog terraform toml +# typescript typescript_vts vue yaml zig # (This list may be outdated. For the current list, see values of Language enum here: # https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py # For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) # Note: # - For C, use cpp # - For JavaScript, use typescript +# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root) +# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm) +# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three) # - For Free Pascal/Lazarus, use pascal # Special requirements: # Some languages require additional setup/installations. @@ -30,7 +33,7 @@ project_name: "serena" # Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. languages: - python -- typescript +- svelte # whether to use project's .gitignore files to ignore files ignore_all_files_in_gitignore: true diff --git a/dashboard/.gitignore b/dashboard/.gitignore new file mode 100644 index 000000000..1cf7629d6 --- /dev/null +++ b/dashboard/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +.vite +*.local diff --git a/dashboard/.prettierignore b/dashboard/.prettierignore new file mode 100644 index 000000000..16acd49d7 --- /dev/null +++ b/dashboard/.prettierignore @@ -0,0 +1,3 @@ +node_modules +dist +package-lock.json diff --git a/dashboard/.prettierrc b/dashboard/.prettierrc new file mode 100644 index 000000000..531cdc877 --- /dev/null +++ b/dashboard/.prettierrc @@ -0,0 +1 @@ +{ "singleQuote": true, "printWidth": 100, "plugins": ["prettier-plugin-svelte"] } diff --git a/dashboard/CLAUDE.md b/dashboard/CLAUDE.md new file mode 100644 index 000000000..38b16e245 --- /dev/null +++ b/dashboard/CLAUDE.md @@ -0,0 +1,120 @@ +# Serena Dashboard (frontend) + +Svelte 5 (runes) + TS + Vite SPA. **Frontend only** — the HTTP API lives in +`../src/serena/dashboard.py` and is a **frozen contract**: never change endpoint +names, request/response shapes, ports, or the host-header check from here. The +canonical list of routes the app calls is `API_ROUTES` in `vite.config.ts`. + +## Commands (run from `dashboard/`) + +- `npm run dev` — Vite on :5273, proxies API routes to a backend on :24282 +- `npm run build` — hashed assets into `../src/serena/resources/dashboard/` +- `npm run check` — `svelte-check` · `npm test` — Vitest · `npm run lint` / `format` + +For `npm run dev` start any Serena MCP server with the dashboard enabled first +(logos/icons 404 under dev — they're served by the backend). See `README.md` for +the human quick-start. + +## The build-output contract (CI enforces) + +After **any** source change run `npm run build` and commit the regenerated +`../src/serena/resources/dashboard/` (`index.html` + `assets/`) — it ships in the +wheel and CI fails the PR if it's stale. Before committing run `npm run format` +and **stage all changes** (a partial stage can leave other files prettier-dirty +and fail CI's `prettier --check`). `prebuild` (`scripts/clean-assets.mjs`) clears +`assets/` first because `emptyOutDir: false` (the dir also holds icon/logo PNGs). + +## Architecture rules + +- Components are small, single-purpose: props in, events out (`on*` callback + props, not `createEventDispatcher`), scoped CSS. Compose `common/` primitives + (`Card`, `Button`, `Modal`, `Spinner`, `Combobox`, `Collapsible`) — don't + re-implement their markup. +- Colors come from `src/styles/tokens.css` (light + `[data-theme='dark']`). + Never hardcode hex; use `var(--token)`. +- Charts go **only** through `src/components/stats/ChartPanel.svelte`. +- Never `fetch` from a component — all network goes through `src/lib/api/`. +- Never reintroduce jQuery. + +## State: runes stores (`src/lib/stores/*.svelte.ts`) + +`$state` only lives in `.svelte.ts` modules. Each store is a factory returning +**getter-only** accessors plus action methods, exported as a singleton: + +```ts +export function createXStore() { + let data = $state(null); + return { + get data() { + return data; + }, + async poll() { + data = await fetchX(); + }, + }; +} +export const x = createXStore(); // import the singleton; factory exists for tests +``` + +Never expose the `$state` variable directly — only getters, so reads stay +reactive and writes funnel through methods. + +## API layer (`src/lib/api/`) + +- `types.ts` — TS mirrors of backend JSON. `endpoints.ts` — one typed fn per + route. `client.ts` — the **only** place that calls `fetch` (`getJson` / + `postJson` / `putJson`, throws `ApiError` on non-2xx). +- **Two failure channels.** The backend signals failure either as a non-2xx + (thrown `ApiError`) _or_ as HTTP 200 with `{ status: 'error', message }`. Wrap + every mutating call in `runMutation(fn)` (`mutation.ts`) — it normalizes both + into `{ ok, message?, data? }`. Don't hand-roll try/catch around endpoints. + +## Modals + +- One discriminated union `ModalState` in `stores/modal.svelte.ts`; the global + `modal` store has `open(state)` / `close()`. `App.svelte` opens modals; + `ModalHost.svelte` is the single switch that renders the active one. To add a + modal: extend the union, add a case in `ModalHost`, build the component. +- Mutating modals use `createModalAction()` (`modalAction.svelte.ts`) for the + shared busy/error lifecycle: `action.run(() => runMutation(...), onclose)` — + on success it calls `onclose`, on failure it sets `action.error` and stays + open. Confirm-style modals just wrap `ConfirmModal`. +- Editor modals guard unsaved work with `confirmDiscard(isDirty)` before closing. +- `Modal.svelte` owns a11y: focus trap, focus restore on destroy, Escape/backdrop + close. Don't re-implement dialog behavior per modal. + +## Polling + +- `createPoller(fn, intervalMs)` (`polling.ts`) self-guards against overlapping + ticks (`inFlight`). `pollersForView(view)` (`pollers.ts`) is a **pure** map of + view → which pollers run, so it's unit-tested independently. `App.svelte` stops + all pollers and starts the view's set on every `navigate`. Poll calls are + wrapped in `safe()` so a transient backend error logs instead of throwing an + unhandled rejection; the next tick retries. + +## Charts (`ChartPanel.svelte`) + +Frappe Charts wrapper. Series colors are read from CSS vars at construction. A +**create-effect** rebuilds the chart only on theme change / element rebind +(`data`/`title`/`type` read via `untrack`); a separate **update-effect** diffs +`data` in place — full teardown triggered a ResizeObserver `removeChild` race. A +module-level error suppressor still swallows exactly that benign `NotFoundError` +(the ResizeObserver fires it on initial grid layout / unmount). Keep both. + +## Testing (Vitest + jsdom + Testing Library) + +- Helpers in `tests/helpers.ts`: `stubFetchJson` / `stubFetchRoutes` (substring + URL routing), `errBody` / `okBody` (the two backend channels), `exec()` + fixtures. `tests/setup.ts` restores mocks after each test. +- Test the singleton store via its factory (`createXStore()`) for isolation. +- A store/lib gets a unit test; a component gets a render + interaction test + (assert error-stays-open / success-closes for modals). + +## Gotchas + +- **frappe-charts@1.6.2** ships no CSS and no types — import only the JS; ambient + types live in `src/types/frappe-charts.d.ts`. Don't add a `dist/*.css` import. +- **Node 26 + Vitest:** Node's experimental `localStorage` shadows jsdom's, so + `tests/setup.ts` reinstalls it; localStorage tests must `clear()` in `beforeEach`. +- `$state(someProp)` (prop as initial state) emits a `state_referenced_locally` + svelte-check **warning**, not an error — suppressed where intentional. diff --git a/dashboard/README.md b/dashboard/README.md new file mode 100644 index 000000000..0ec4c81ef --- /dev/null +++ b/dashboard/README.md @@ -0,0 +1,89 @@ +# Serena Dashboard (frontend) + +Svelte 5 (runes) + TypeScript + Vite single-page app. It is built into +`../src/serena/resources/dashboard/` and served by the Flask backend in +`../src/serena/dashboard.py` at `/dashboard/`. The backend is a **frozen +contract** — don't change endpoint names, shapes, or ports from here. + +> `CLAUDE.md` in this folder has the architecture rules for AI agents; this +> README is the human quick-start. + +## Quick start + +```bash +cd dashboard +npm install +npm run dev # Vite dev server on :5273, proxies API routes to a backend on :24282 +``` + +`npm run dev` only serves the frontend — start a Serena MCP server (dashboard +enabled, default port **24282**) first so the API calls resolve. Note: the +logo/icon files are served by the backend, so they 404 under `npm run dev`. For +full fidelity (logos, real data, correct routing) build and open the app the way +it ships: + +```bash +npm run build # writes hashed assets into ../src/serena/resources/dashboard/ +# then open http://localhost:24282/dashboard/ with a Serena server running +``` + +## Commands + +| Command | What | +| --------------------------------- | --------------------------------------------- | +| `npm run check` | Type-check (`svelte-check`) | +| `npm test` / `npm run test:watch` | Vitest | +| `npm run lint` / `npm run format` | ESLint + Prettier (check / write) | +| `npm run build` | Production build into the Python resource dir | + +## The build-output contract (important) + +The build output under `../src/serena/resources/dashboard/` (`index.html` + +`assets/`) is **committed to git** and shipped in the wheel. After any source +change: **`npm run build` and commit the regenerated output.** CI +(`.github/workflows/dashboard.yml`) rebuilds and fails the PR if the committed +output is stale. There is also a poe task: `poe build-dashboard`. + +`npm run build` auto-runs a `prebuild` step (`scripts/clean-assets.mjs`) that +clears `assets/` first. This prevents stale hashed bundles from piling up: +`emptyOutDir: false` is required because the same output dir holds the icon/logo +files, so Vite can't clean it on its own. + +**Before committing:** run `npm run format` and **stage all changes**. Committing +only task-scoped files can leave other files prettier-dirty and fail CI's +`prettier --check`. + +## Layout + +- `src/lib/api/` — `types.ts` (mirrors backend JSON), `endpoints.ts` (one typed + fn per route), `client.ts` (the only place that calls `fetch` for the API). +- `src/lib/stores/` — runes stores (`*.svelte.ts`): config, logs, executions, + stats, theme, modal. +- `src/lib/` — `polling.ts`, `format.ts`, `charts.ts`, `validation.ts`, `banners.ts`. +- `src/components/` — `common/`, `shell/`, `overview/`, `logs/`, `stats/`, + `modals/`, `banners/`. +- `src/styles/tokens.css` — the palette (light + `[data-theme='dark']`). Never + hardcode hex in a component; use `var(--token)`. +- `tests/` — Vitest specs. + +## Adding a feature that needs a backend route + +1. Add the request/response type to `src/lib/api/types.ts`. +2. Add a typed function to `src/lib/api/endpoints.ts`. +3. Add/extend a store under `src/lib/stores/`. +4. Build the component + a Vitest test, then rebuild and commit the output. + +## Gotchas + +- **frappe-charts@1.6.2 ships no compiled CSS** — `ChartPanel.svelte` imports + only the JS. Don't re-add a `frappe-charts/dist/*.css` import; it breaks the + build. The library also has no TS types — ambient declaration lives in + `src/types/frappe-charts.d.ts`. +- **Node 26 + Vitest:** Node's experimental `localStorage` global shadows + jsdom's, so `tests/setup.ts` reinstalls jsdom's Storage. Tests that touch + `localStorage` must `clear()` it in `beforeEach`. +- **`$state(someProp)`** (capturing a prop as initial state) emits a + `state_referenced_locally` svelte-check warning; it's suppressed where + intentional. These are warnings, not errors. +- Charts go only through `ChartPanel.svelte`; the chart instance is destroyed in + the `$effect` cleanup to avoid leaking resize listeners on re-render. diff --git a/dashboard/eslint.config.js b/dashboard/eslint.config.js new file mode 100644 index 000000000..5bf00897f --- /dev/null +++ b/dashboard/eslint.config.js @@ -0,0 +1,31 @@ +import js from '@eslint/js'; +import svelte from 'eslint-plugin-svelte'; +import svelteParser from 'svelte-eslint-parser'; +import tsParser from '@typescript-eslint/parser'; +import globals from 'globals'; + +export default [ + js.configs.recommended, + ...svelte.configs['flat/recommended'], + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + }, + rules: { + 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + }, + }, + { + files: ['**/*.svelte'], + languageOptions: { + parser: svelteParser, + parserOptions: { + parser: tsParser, + }, + }, + }, + { ignores: ['node_modules/', 'dist/'] }, +]; diff --git a/dashboard/index.html b/dashboard/index.html new file mode 100644 index 000000000..6770d7649 --- /dev/null +++ b/dashboard/index.html @@ -0,0 +1,21 @@ + + + + + + Serena Dashboard + + + + + + + + +
+ + + diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json new file mode 100644 index 000000000..6c2bed5e8 --- /dev/null +++ b/dashboard/package-lock.json @@ -0,0 +1,5504 @@ +{ + "name": "serena-dashboard", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "serena-dashboard", + "version": "1.0.0", + "dependencies": { + "frappe-charts": "^1.6.2" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@testing-library/jest-dom": "^6.4.0", + "@testing-library/svelte": "^5.2.0", + "@tsconfig/svelte": "^5.0.0", + "@typescript-eslint/parser": "^8.60.0", + "eslint": "^9.0.0", + "eslint-plugin-svelte": "^2.46.0", + "jsdom": "^25.0.0", + "prettier": "^3.3.0", + "prettier-plugin-svelte": "^3.3.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "^5.6.0", + "vite": "^6.0.0", + "vitest": "^2.1.0" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.10.tgz", + "integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", + "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.1", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.17", + "vitefu": "^1.0.6" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/svelte": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.3.1.tgz", + "integrity": "sha512-8Ez7ZOqW5geRf9PF5rkuopODe5RGy3I9XR+kc7zHh26gBiktLaxTfKmhlGaSHYUOTQE7wFsLMN9xCJVCszw47w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@testing-library/dom": "9.x.x || 10.x.x", + "@testing-library/svelte-core": "1.0.0" + }, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0", + "vite": "*", + "vitest": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@testing-library/svelte-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/svelte-core/-/svelte-core-1.0.0.tgz", + "integrity": "sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0" + } + }, + "node_modules/@tsconfig/svelte": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.8.tgz", + "integrity": "sha512-UkNnw1/oFEfecR8ypyHIQuWYdkPvHiwcQ78sh+ymIiYoF+uc5H1UBetbjyqT+vgGJ3qQN6nhucJviX6HesWtKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz", + "integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", + "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.60.0", + "@typescript-eslint/types": "^8.60.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", + "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", + "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", + "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", + "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.60.0", + "@typescript-eslint/tsconfig-utils": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", + "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devalue": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "2.46.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.1.tgz", + "integrity": "sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@jridgewell/sourcemap-codec": "^1.4.15", + "eslint-compat-utils": "^0.5.1", + "esutils": "^2.0.3", + "known-css-properties": "^0.35.0", + "postcss": "^8.4.38", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.1.0", + "semver": "^7.6.2", + "svelte-eslint-parser": "^0.43.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.9.tgz", + "integrity": "sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "peerDependencies": { + "@typescript-eslint/types": "^8.2.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/types": { + "optional": true + } + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/frappe-charts": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/frappe-charts/-/frappe-charts-1.6.2.tgz", + "integrity": "sha512-9TC3/+YVUi84yYoEbxFiSqu+1FQ5If/ydUNj6i8FRpwynd08t6a7RkS+IRJozAk6NfdL8/LVTTE1DUOjjKZZxg==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz", + "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nwsapi": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.2.tgz", + "integrity": "sha512-ItFouLvzSFE3ulNl4DKoWM3BGcbDCNVpIyy/Y3F2gC3aNiGLxtFUdffVqO5Z5hhYG+DFT5KULWaxmeFFpdbvaQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/svelte": { + "version": "5.55.9", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.9.tgz", + "integrity": "sha512-fTjjT8cHLDwigcu2j3pv7Jq04LklXevPB8uBgyHNiTXv+RMNvVnrjS4UEYrLMkhuq1vpCodHjiW+z/95SDs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.10", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.8.1", + "esm-env": "^1.2.1", + "esrap": "^2.2.9", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.8.tgz", + "integrity": "sha512-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", + "integrity": "sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "postcss": "^8.4.39", + "postcss-scss": "^4.0.9" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte-eslint-parser/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/svelte-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/svelte-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/svelte/node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vite-node/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/dashboard/package.json b/dashboard/package.json new file mode 100644 index 000000000..865099422 --- /dev/null +++ b/dashboard/package.json @@ -0,0 +1,37 @@ +{ + "name": "serena-dashboard", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "prebuild": "node scripts/clean-assets.mjs", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json", + "test": "vitest run", + "test:watch": "vitest", + "lint": "eslint . && prettier --check .", + "format": "prettier --write ." + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@testing-library/jest-dom": "^6.4.0", + "@testing-library/svelte": "^5.2.0", + "@tsconfig/svelte": "^5.0.0", + "@typescript-eslint/parser": "^8.60.0", + "eslint": "^9.0.0", + "eslint-plugin-svelte": "^2.46.0", + "jsdom": "^25.0.0", + "prettier": "^3.3.0", + "prettier-plugin-svelte": "^3.3.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "^5.6.0", + "vite": "^6.0.0", + "vitest": "^2.1.0" + }, + "dependencies": { + "frappe-charts": "^1.6.2" + } +} diff --git a/dashboard/scripts/clean-assets.mjs b/dashboard/scripts/clean-assets.mjs new file mode 100644 index 000000000..f4af3a9c3 --- /dev/null +++ b/dashboard/scripts/clean-assets.mjs @@ -0,0 +1,4 @@ +import { rmSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +const dir = fileURLToPath(new URL('../../src/serena/resources/dashboard/assets', import.meta.url)); +rmSync(dir, { recursive: true, force: true }); diff --git a/dashboard/src/App.svelte b/dashboard/src/App.svelte new file mode 100644 index 000000000..43bc5c384 --- /dev/null +++ b/dashboard/src/App.svelte @@ -0,0 +1,87 @@ + + +
+
modal.open({ kind: 'shutdown' })} /> +
+ {#if view === 'overview'}
+ modal.open({ kind: 'addLanguage' })} + onremovelanguage={(language) => modal.open({ kind: 'removeLanguage', language })} + oneditconfig={() => modal.open({ kind: 'editSerenaConfig' })} + onopenmemory={(name) => modal.open({ kind: 'editMemory', name })} + oncreatememory={() => modal.open({ kind: 'createMemory' })} + ondeletememory={(name) => modal.open({ kind: 'deleteMemory', name })} + oncancelexecution={(ex) => + ex.is_running + ? modal.open({ kind: 'cancelExecution', execution: ex }) + : void executions.cancel(ex)} + /> +
{/if} + {#if view === 'logs'}
{/if} + {#if view === 'stats'}
{/if} +
+
+ + + + diff --git a/dashboard/src/components/banners/BannerCarousel.svelte b/dashboard/src/components/banners/BannerCarousel.svelte new file mode 100644 index 000000000..2e0cd6e27 --- /dev/null +++ b/dashboard/src/components/banners/BannerCarousel.svelte @@ -0,0 +1,65 @@ + + +{#if current} + +{/if} + + diff --git a/dashboard/src/components/common/Button.svelte b/dashboard/src/components/common/Button.svelte new file mode 100644 index 000000000..e3f88cd64 --- /dev/null +++ b/dashboard/src/components/common/Button.svelte @@ -0,0 +1,49 @@ + + + + + diff --git a/dashboard/src/components/common/Card.svelte b/dashboard/src/components/common/Card.svelte new file mode 100644 index 000000000..df61b3291 --- /dev/null +++ b/dashboard/src/components/common/Card.svelte @@ -0,0 +1,28 @@ + + +
+ {#if title}

{title}

{/if} + {@render children()} +
+ + diff --git a/dashboard/src/components/common/Collapsible.svelte b/dashboard/src/components/common/Collapsible.svelte new file mode 100644 index 000000000..8255f66ca --- /dev/null +++ b/dashboard/src/components/common/Collapsible.svelte @@ -0,0 +1,73 @@ + + +
+

+ +

+ {#if expanded} +
{@render children()}
+ {/if} +
+ + diff --git a/dashboard/src/components/common/Combobox.svelte b/dashboard/src/components/common/Combobox.svelte new file mode 100644 index 000000000..e3062d339 --- /dev/null +++ b/dashboard/src/components/common/Combobox.svelte @@ -0,0 +1,126 @@ + + +
+ (openList = true)} + oninput={() => (openList = true)} + autocomplete="off" + spellcheck="false" + /> + + {#if openList} + {#if filtered.length} +
    + {#each filtered as o (o)} +
  • choose(o)} + onkeydown={(e) => onOptionKey(e, o)} + > + {o} +
  • + {/each} +
+ {:else} +
No options available
+ {/if} + {/if} +
+ + diff --git a/dashboard/src/components/common/Modal.svelte b/dashboard/src/components/common/Modal.svelte new file mode 100644 index 000000000..5f3350e86 --- /dev/null +++ b/dashboard/src/components/common/Modal.svelte @@ -0,0 +1,121 @@ + + + + +{#if open} + +{/if} + + diff --git a/dashboard/src/components/common/Spinner.svelte b/dashboard/src/components/common/Spinner.svelte new file mode 100644 index 000000000..35cb5f4b6 --- /dev/null +++ b/dashboard/src/components/common/Spinner.svelte @@ -0,0 +1,19 @@ +
+ + diff --git a/dashboard/src/components/logs/LogToolbar.svelte b/dashboard/src/components/logs/LogToolbar.svelte new file mode 100644 index 000000000..a8f176782 --- /dev/null +++ b/dashboard/src/components/logs/LogToolbar.svelte @@ -0,0 +1,61 @@ + + +
+ + + +
+ + diff --git a/dashboard/src/components/logs/LogViewer.svelte b/dashboard/src/components/logs/LogViewer.svelte new file mode 100644 index 000000000..35eb287bb --- /dev/null +++ b/dashboard/src/components/logs/LogViewer.svelte @@ -0,0 +1,56 @@ + + +
+ {#each lines as line, i (i)} + +
{@html highlightTools(line, toolNames)}
+ {/each} +
+ + diff --git a/dashboard/src/components/logs/LogsPage.svelte b/dashboard/src/components/logs/LogsPage.svelte new file mode 100644 index 000000000..d3b813c2c --- /dev/null +++ b/dashboard/src/components/logs/LogsPage.svelte @@ -0,0 +1,16 @@ + + + logs.clear()} /> + diff --git a/dashboard/src/components/modals/AddLanguageModal.svelte b/dashboard/src/components/modals/AddLanguageModal.svelte new file mode 100644 index 000000000..b7f4c48c0 --- /dev/null +++ b/dashboard/src/components/modals/AddLanguageModal.svelte @@ -0,0 +1,40 @@ + + + + + + (selected = v)} /> + + diff --git a/dashboard/src/components/modals/CancelExecutionModal.svelte b/dashboard/src/components/modals/CancelExecutionModal.svelte new file mode 100644 index 000000000..5b296ab76 --- /dev/null +++ b/dashboard/src/components/modals/CancelExecutionModal.svelte @@ -0,0 +1,17 @@ + + + executions.cancel(execution)} onclose={handleClose}> + Are you sure? The execution will continue running until timeout, it will simply no longer be in + the queue. Abandoning a running execution is only advised as a measure for unblocking Serena. + diff --git a/dashboard/src/components/modals/ConfirmModal.svelte b/dashboard/src/components/modals/ConfirmModal.svelte new file mode 100644 index 000000000..48e31f3fb --- /dev/null +++ b/dashboard/src/components/modals/ConfirmModal.svelte @@ -0,0 +1,34 @@ + + + + + + diff --git a/dashboard/src/components/modals/CreateMemoryModal.svelte b/dashboard/src/components/modals/CreateMemoryModal.svelte new file mode 100644 index 000000000..3c8b36cbe --- /dev/null +++ b/dashboard/src/components/modals/CreateMemoryModal.svelte @@ -0,0 +1,44 @@ + + + + + + + + diff --git a/dashboard/src/components/modals/DeleteMemoryModal.svelte b/dashboard/src/components/modals/DeleteMemoryModal.svelte new file mode 100644 index 000000000..08faddd68 --- /dev/null +++ b/dashboard/src/components/modals/DeleteMemoryModal.svelte @@ -0,0 +1,10 @@ + + + runMutation(() => deleteMemory(name))} {onclose}> + Delete memory {name}? + diff --git a/dashboard/src/components/modals/EditMemoryModal.svelte b/dashboard/src/components/modals/EditMemoryModal.svelte new file mode 100644 index 000000000..706fcc75e --- /dev/null +++ b/dashboard/src/components/modals/EditMemoryModal.svelte @@ -0,0 +1,116 @@ + + + + + + + + + diff --git a/dashboard/src/components/modals/EditSerenaConfigModal.svelte b/dashboard/src/components/modals/EditSerenaConfigModal.svelte new file mode 100644 index 000000000..49e38a2f0 --- /dev/null +++ b/dashboard/src/components/modals/EditSerenaConfigModal.svelte @@ -0,0 +1,40 @@ + + + + + + + diff --git a/dashboard/src/components/modals/ModalHost.svelte b/dashboard/src/components/modals/ModalHost.svelte new file mode 100644 index 000000000..dfc71ac62 --- /dev/null +++ b/dashboard/src/components/modals/ModalHost.svelte @@ -0,0 +1,41 @@ + + +{#if m} + {#if m.kind === 'shutdown'} + {:else if m.kind === 'cancelExecution'} + {:else if m.kind === 'addLanguage'} + {:else if m.kind === 'removeLanguage'} + {:else if m.kind === 'editMemory'}{#key m.name}{/key} + {:else if m.kind === 'createMemory'} + modal.open({ kind: 'editMemory', name })} + /> + {:else if m.kind === 'deleteMemory'} + {:else if m.kind === 'editSerenaConfig'} + {/if} +{/if} diff --git a/dashboard/src/components/modals/RemoveLanguageModal.svelte b/dashboard/src/components/modals/RemoveLanguageModal.svelte new file mode 100644 index 000000000..c8a454efa --- /dev/null +++ b/dashboard/src/components/modals/RemoveLanguageModal.svelte @@ -0,0 +1,10 @@ + + + runMutation(() => removeLanguage(language))} {onclose}> + Remove language {language} from configuration? + diff --git a/dashboard/src/components/modals/ShutdownModal.svelte b/dashboard/src/components/modals/ShutdownModal.svelte new file mode 100644 index 000000000..ec05e4033 --- /dev/null +++ b/dashboard/src/components/modals/ShutdownModal.svelte @@ -0,0 +1,22 @@ + + + + Shut down the Serena server? + diff --git a/dashboard/src/components/overview/CancelledExecutions.svelte b/dashboard/src/components/overview/CancelledExecutions.svelte new file mode 100644 index 000000000..d472a9e8d --- /dev/null +++ b/dashboard/src/components/overview/CancelledExecutions.svelte @@ -0,0 +1,44 @@ + + +
+ {#each items as ex (ex.task_id)} +
+ + {ex.name} + {ex.is_running ? 'abandoned · ' : ''}#{ex.task_id} +
+ {/each} +
+ + diff --git a/dashboard/src/components/overview/ConfigCard.svelte b/dashboard/src/components/overview/ConfigCard.svelte new file mode 100644 index 000000000..4a7f3c1fe --- /dev/null +++ b/dashboard/src/components/overview/ConfigCard.svelte @@ -0,0 +1,254 @@ + + +
+
+ Version + {data.serena_version} + + Active Project + + {#if data.active_project?.name && data.active_project?.path} + {data.active_project.name} + {:else} + {data.active_project?.name ?? 'None'} + {/if} + + + Languages + + {#if data.jetbrains_mode} + Using JetBrains backend + {:else} + + {#each data.languages as lang (lang)} + {lang} + {#if data.languages.length > 1} + + {/if} + + {/each} + {#if data.active_project?.name} + + {/if} + + {/if} + + + Context + {data.context.name} + + Active Modes + + {#if data.modes.length} + {#each data.modes as m, i (m.name)}{m.name}{#if i < data.modes.length - 1}, + {/if}{/each} + {:else} + None + {/if} + + + File Encoding + {data.encoding ?? 'N/A'} +
+ + +
+ {#each data.active_tools as t (t)}{t}{/each} +
+
+ + {#if data.available_memories} + +
+ {#each data.available_memories as m (m)} +
+ + +
+ {/each} + +
+
+ {/if} + + +
+ + diff --git a/dashboard/src/components/overview/ExecutionsQueue.svelte b/dashboard/src/components/overview/ExecutionsQueue.svelte new file mode 100644 index 000000000..1a77f94a3 --- /dev/null +++ b/dashboard/src/components/overview/ExecutionsQueue.svelte @@ -0,0 +1,67 @@ + + +{#if visible.length} +
+ {#each visible as ex (ex.task_id)} +
+ {#if ex.is_running}{/if} + {ex.name} + +
+ {/each} +
+{:else} +
No queued executions.
+{/if} +{#if cancelError}{/if} + + diff --git a/dashboard/src/components/overview/LastExecution.svelte b/dashboard/src/components/overview/LastExecution.svelte new file mode 100644 index 000000000..5ee33189a --- /dev/null +++ b/dashboard/src/components/overview/LastExecution.svelte @@ -0,0 +1,94 @@ + + +{#if execution} +
+ +
+ {statusWord} + {execution.name} +
+ #{execution.task_id} +
+{:else} +
None yet.
+{/if} + + diff --git a/dashboard/src/components/overview/ListPanel.svelte b/dashboard/src/components/overview/ListPanel.svelte new file mode 100644 index 000000000..f5f2fc6d6 --- /dev/null +++ b/dashboard/src/components/overview/ListPanel.svelte @@ -0,0 +1,48 @@ + + + + + {#if items.length} +
    + {#each items as item (item.name)}
  • {item.name}
  • {/each} +
+ {:else} +
None.
+ {/if} +
+
+ + diff --git a/dashboard/src/components/overview/NewsSection.svelte b/dashboard/src/components/overview/NewsSection.svelte new file mode 100644 index 000000000..a8224fc26 --- /dev/null +++ b/dashboard/src/components/overview/NewsSection.svelte @@ -0,0 +1,49 @@ + + +{#if items.length} +
+

What's New

+ {#each items as [id, html] (id)} +
+ +
{@html html}
+ +
+ {/each} +
+{/if} + + diff --git a/dashboard/src/components/overview/OverviewPage.svelte b/dashboard/src/components/overview/OverviewPage.svelte new file mode 100644 index 000000000..cbcf02f55 --- /dev/null +++ b/dashboard/src/components/overview/OverviewPage.svelte @@ -0,0 +1,104 @@ + + +{#if !d} + +{:else} +
+
+ + + + + + + + + + + {#if executions.cancelled.length} + + + + {/if} + + + +
+
+ + !t.is_active).map((t) => ({ name: t.name }))} + /> + ({ name: m.name, active: m.is_active }))} + /> + ({ name: c.name, active: c.is_active }))} + /> + + + +
+
+{/if} + + diff --git a/dashboard/src/components/overview/ProjectsPanel.svelte b/dashboard/src/components/overview/ProjectsPanel.svelte new file mode 100644 index 000000000..c9c4fbf60 --- /dev/null +++ b/dashboard/src/components/overview/ProjectsPanel.svelte @@ -0,0 +1,68 @@ + + + + + {#if projects.length} +
    + {#each projects as p (p.path)} +
  • +
    {p.name}
    +
    {p.path}
    +
  • + {/each} +
+ {:else} +
None.
+ {/if} +
+
+ + diff --git a/dashboard/src/components/overview/ToolUsageBars.svelte b/dashboard/src/components/overview/ToolUsageBars.svelte new file mode 100644 index 000000000..2ba36c2a2 --- /dev/null +++ b/dashboard/src/components/overview/ToolUsageBars.svelte @@ -0,0 +1,53 @@ + + +{#if sorted.length} +
+ {#each sorted as [name, s] (name)} +
+ {name} +
+
+
+ {s.num_calls} +
+ {/each} +
+{:else} +
No tool usage yet.
+{/if} + + diff --git a/dashboard/src/components/shell/Header.svelte b/dashboard/src/components/shell/Header.svelte new file mode 100644 index 000000000..67c180ee5 --- /dev/null +++ b/dashboard/src/components/shell/Header.svelte @@ -0,0 +1,118 @@ + + +
+
+
+
+
+ +
+ + diff --git a/dashboard/src/components/shell/ThemeToggle.svelte b/dashboard/src/components/shell/ThemeToggle.svelte new file mode 100644 index 000000000..73566b6d4 --- /dev/null +++ b/dashboard/src/components/shell/ThemeToggle.svelte @@ -0,0 +1,22 @@ + + + + + diff --git a/dashboard/src/components/stats/ChartPanel.svelte b/dashboard/src/components/stats/ChartPanel.svelte new file mode 100644 index 000000000..74a464910 --- /dev/null +++ b/dashboard/src/components/stats/ChartPanel.svelte @@ -0,0 +1,118 @@ + + + + +
+

{title}

+
+
+ + diff --git a/dashboard/src/components/stats/StatsPage.svelte b/dashboard/src/components/stats/StatsPage.svelte new file mode 100644 index 000000000..ae37d1298 --- /dev/null +++ b/dashboard/src/components/stats/StatsPage.svelte @@ -0,0 +1,75 @@ + + +
+ + +
+ +{#if hasStats} + +
Token estimator: {stats.estimator}
+
+ + + +
+
+ + +
+{:else} +
No tool stats collected yet.
+{/if} + + diff --git a/dashboard/src/components/stats/StatsSummary.svelte b/dashboard/src/components/stats/StatsSummary.svelte new file mode 100644 index 000000000..f9ea59d0f --- /dev/null +++ b/dashboard/src/components/stats/StatsSummary.svelte @@ -0,0 +1,35 @@ + + + + + + + + + +
Total calls{totals.calls}
Total input tokens{totals.input}
Total output tokens{totals.output}
Total tokens{totals.input + totals.output}
+ + diff --git a/dashboard/src/lib/api/client.ts b/dashboard/src/lib/api/client.ts new file mode 100644 index 000000000..ebea43aa8 --- /dev/null +++ b/dashboard/src/lib/api/client.ts @@ -0,0 +1,31 @@ +export class ApiError extends Error { + constructor( + public status: number, + message: string, + ) { + super(message); + } +} + +async function handle(res: Response): Promise { + if (!res.ok) throw new ApiError(res.status, `HTTP ${res.status} for ${res.url}`); + return (await res.json()) as T; +} + +export async function getJson(path: string): Promise { + return handle(await fetch(path)); +} + +export async function postJson(path: string, body?: unknown): Promise { + return handle( + await fetch(path, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body ?? {}), + }), + ); +} + +export async function putJson(path: string): Promise { + return handle(await fetch(path, { method: 'PUT' })); +} diff --git a/dashboard/src/lib/api/endpoints.ts b/dashboard/src/lib/api/endpoints.ts new file mode 100644 index 000000000..c6556a36a --- /dev/null +++ b/dashboard/src/lib/api/endpoints.ts @@ -0,0 +1,52 @@ +import { getJson, postJson, putJson } from './client'; +import type { + ResponseLog, + ResponseToolNames, + ResponseToolStats, + ResponseConfigOverview, + ResponseAvailableLanguages, + ResponseGetMemory, + ResponseGetSerenaConfig, + ResponseQueuedExecutions, + ResponseLastExecution, + ResponseCancelExecution, + ResponseNews, + StatusResponse, + TokenEstimatorResponse, +} from './types'; + +export const fetchConfigOverview = () => getJson('/get_config_overview'); +export const fetchLogMessages = (startIdx: number) => + postJson('/get_log_messages', { start_idx: startIdx }); +export const clearLogs = () => postJson('/clear_logs'); +export const fetchToolNames = () => getJson('/get_tool_names'); +export const fetchToolStats = () => getJson('/get_tool_stats'); +export const clearToolStats = () => postJson('/clear_tool_stats'); +export const fetchEstimatorName = () => + getJson('/get_token_count_estimator_name'); +export const shutdown = () => putJson('/shutdown'); +export const fetchAvailableLanguages = () => + getJson('/get_available_languages'); +export const addLanguage = (language: string) => + postJson('/add_language', { language }); +export const removeLanguage = (language: string) => + postJson('/remove_language', { language }); +export const getMemory = (memory_name: string) => + postJson('/get_memory', { memory_name }); +export const saveMemory = (memory_name: string, content: string) => + postJson('/save_memory', { memory_name, content }); +export const deleteMemory = (memory_name: string) => + postJson('/delete_memory', { memory_name }); +export const renameMemory = (old_name: string, new_name: string) => + postJson('/rename_memory', { old_name, new_name }); +export const getSerenaConfig = () => getJson('/get_serena_config'); +export const saveSerenaConfig = (content: string) => + postJson('/save_serena_config', { content }); +export const fetchQueuedExecutions = () => + getJson('/queued_task_executions'); +export const cancelExecution = (task_id: number) => + postJson('/cancel_task_execution', { task_id }); +export const fetchLastExecution = () => getJson('/last_execution'); +export const fetchUnreadNews = () => getJson('/fetch_unread_news'); +export const markNewsRead = (news_snippet_id: string) => + postJson('/mark_news_snippet_as_read', { news_snippet_id }); diff --git a/dashboard/src/lib/api/mutation.ts b/dashboard/src/lib/api/mutation.ts new file mode 100644 index 000000000..f89aa4810 --- /dev/null +++ b/dashboard/src/lib/api/mutation.ts @@ -0,0 +1,23 @@ +export interface MutationResult { + ok: boolean; + message?: string; + data?: T; +} + +/** + * Run a mutation endpoint call and normalize both failure channels: + * a thrown ApiError (non-2xx) AND a 200-OK body of { status: 'error', message }. + */ +export async function runMutation( + fn: () => Promise, +): Promise> { + try { + const data = await fn(); + if (data && data.status === 'error') { + return { ok: false, message: data.message ?? 'Request failed', data }; + } + return { ok: true, data }; + } catch (e) { + return { ok: false, message: e instanceof Error ? e.message : 'Request failed' }; + } +} diff --git a/dashboard/src/lib/api/types.ts b/dashboard/src/lib/api/types.ts new file mode 100644 index 000000000..3292a4c4e --- /dev/null +++ b/dashboard/src/lib/api/types.ts @@ -0,0 +1,126 @@ +// Mirrors src/serena/dashboard.py response/request models. Field names must match JSON. + +export interface ToolStatEntry { + num_times_called: number; + input_tokens: number; + output_tokens: number; +} +export type ToolStats = Record; + +// NOTE: /get_config_overview's tool_stats_summary is a DIFFERENT, reduced shape — +// the backend renames num_times_called -> num_calls and drops the token fields +// (dashboard.py:564). It is NOT a ToolStats. +export interface ToolSummaryEntry { + num_calls: number; +} +export type ToolStatsSummary = Record; + +export interface ResponseLog { + messages: string[]; + max_idx: number; + active_project: string | null; +} + +export interface ResponseToolNames { + tool_names: string[]; +} +export interface ResponseToolStats { + stats: ToolStats; +} + +// Precise nested shapes verified against ResponseConfigOverview in dashboard.py. +export interface ActiveProject { + name: string | null; + language: string | null; // comma-separated, e.g. "Python, TypeScript" + path: string | null; +} +export interface ContextInfo { + name: string; + description: string; + path: string; +} +export interface ModeInfo { + name: string; + description?: string; + path: string; + is_active?: boolean; +} +export interface ProjectInfo { + name: string; + path: string; + is_active: boolean; +} +export interface ToolInfo { + name: string; + is_active: boolean; +} +export interface ContextOption { + name: string; + is_active: boolean; + path: string; +} + +export interface ResponseConfigOverview { + active_project: ActiveProject | null; + context: ContextInfo; + modes: ModeInfo[]; + active_tools: string[]; + tool_stats_summary: ToolStatsSummary; + registered_projects: ProjectInfo[]; + available_tools: ToolInfo[]; + available_modes: ModeInfo[]; + available_contexts: ContextOption[]; + available_memories: string[] | null; + jetbrains_mode: boolean; + languages: string[]; + encoding: string | null; + current_client: string | null; + serena_version: string; +} + +export interface ResponseAvailableLanguages { + languages: string[]; +} +export interface ResponseGetMemory { + content: string; + memory_name: string; +} +export interface ResponseGetSerenaConfig { + content: string; +} + +export interface QueuedExecution { + task_id: number; + is_running: boolean; + name: string; + finished_successfully: boolean; + logged: boolean; +} +export interface ResponseQueuedExecutions { + queued_executions: QueuedExecution[]; + status: string; +} +export interface ResponseLastExecution { + last_execution: QueuedExecution | null; + status: string; +} +export interface ResponseCancelExecution { + status: string; + was_cancelled: boolean; + message?: string; +} + +// News ids are YYYYMMDD strings; values are HTML snippets. +export interface ResponseNews { + news: Record; + status: string; +} + +// Generic mutation responses ({status, message}) for add/remove/save/delete/rename. +export interface StatusResponse { + status: 'success' | 'error'; + message?: string; +} +export interface TokenEstimatorResponse { + token_count_estimator_name: string; +} diff --git a/dashboard/src/lib/banners.ts b/dashboard/src/lib/banners.ts new file mode 100644 index 000000000..797c5a592 --- /dev/null +++ b/dashboard/src/lib/banners.ts @@ -0,0 +1,50 @@ +import type { Theme } from './stores/theme.svelte'; + +/** + * A single banner entry as returned by the remote manifest. + * Field names mirror the legacy manifest: + * `image` — light-mode image URL (always present) + * `image_dark` — optional dark-mode image URL; falls back to `image` + * `link` — click-through URL + * `alt` — alt text + */ +export interface BannerEntry { + image: string; + image_dark?: string; + link: string; + alt?: string; +} + +/** + * Top-level manifest shape: banners are keyed by sponsorship tier. + */ +export interface BannerManifest { + platinum?: BannerEntry[]; + gold?: BannerEntry[]; +} + +export const BANNER_MANIFEST_URL = 'https://oraios-software.de/serena-banners/manifest.php'; + +/** Return the banners for the given tier (empty array if the key is missing). */ +export function selectBanners( + manifest: BannerManifest, + target: 'platinum' | 'gold', +): BannerEntry[] { + return manifest[target] ?? []; +} + +/** Pick the theme-appropriate image, falling back to the light image. */ +export function pickVariant(entry: BannerEntry, theme: Theme): string { + return theme === 'dark' ? (entry.image_dark ?? entry.image) : entry.image; +} + +/** Fetch the manifest; resilient — returns an empty manifest on any failure. */ +export async function loadManifest(): Promise { + try { + const res = await fetch(BANNER_MANIFEST_URL); + if (!res.ok) return {}; + return (await res.json()) as BannerManifest; + } catch { + return {}; + } +} diff --git a/dashboard/src/lib/charts.ts b/dashboard/src/lib/charts.ts new file mode 100644 index 000000000..f9bbc7752 --- /dev/null +++ b/dashboard/src/lib/charts.ts @@ -0,0 +1,30 @@ +import type { ToolStats, ToolStatEntry } from './api/types'; + +export interface FrappeData { + labels: string[]; + datasets: Array<{ name?: string; values: number[] }>; +} + +function sortedEntries(stats: ToolStats, key: keyof ToolStatEntry): Array<[string, ToolStatEntry]> { + return Object.entries(stats).sort((a, b) => b[1][key] - a[1][key]); +} + +export function toPieData(stats: ToolStats, key: keyof ToolStatEntry): FrappeData { + const entries = sortedEntries(stats, key); + return { + labels: entries.map(([n]) => n), + datasets: [{ values: entries.map(([, s]) => s[key]) }], + }; +} + +export function toSingleSeriesBar( + stats: ToolStats, + key: 'input_tokens' | 'output_tokens', + name: string, +): FrappeData { + const entries = sortedEntries(stats, key); + return { + labels: entries.map(([n]) => n), + datasets: [{ name, values: entries.map(([, s]) => s[key]) }], + }; +} diff --git a/dashboard/src/lib/confirmDiscard.ts b/dashboard/src/lib/confirmDiscard.ts new file mode 100644 index 000000000..534a9f7e5 --- /dev/null +++ b/dashboard/src/lib/confirmDiscard.ts @@ -0,0 +1,4 @@ +/** Returns true if it's safe to close: not dirty, or the user confirmed discarding. */ +export function confirmDiscard(isDirty: boolean): boolean { + return !isDirty || window.confirm('You have unsaved changes. Discard them?'); +} diff --git a/dashboard/src/lib/format.ts b/dashboard/src/lib/format.ts new file mode 100644 index 000000000..5b4f4f2b4 --- /dev/null +++ b/dashboard/src/lib/format.ts @@ -0,0 +1,42 @@ +export type LogLevel = 'debug' | 'info' | 'warning' | 'error'; + +// Serena log lines start with the level name (SERENA_LOG_FORMAT = "%(levelname)-5s ..."). +// Match on the prefix only — the legacy dashboard used `startsWith` — so that the word +// "ERROR" appearing inside an INFO message does not recolor the whole line. +export function detectLevel(line: string): LogLevel { + if (line.startsWith('DEBUG')) return 'debug'; + if (line.startsWith('INFO')) return 'info'; + if (line.startsWith('WARNING')) return 'warning'; + if (line.startsWith('ERROR')) return 'error'; + return 'info'; // legacy `log-default` resolved to the same color as `log-info` +} + +export function escapeHtml(s: string): string { + return s + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function escapeRegExp(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +// Highlight tool names in ONE pass over the escaped text. Building a single combined +// alternation and replacing once means injected markup is never re-scanned, so a +// tool literally named "name"/"span"/"class" can't nest a span into a previous wrapper. +export function highlightTools(text: string, toolNames: string[]): string { + const escaped = escapeHtml(text); + if (toolNames.length === 0) return escaped; + // Longest-first so a longer name wins over a shorter name that is its prefix. + const sorted = [...toolNames].sort((a, b) => b.length - a.length); + // Build a map from lower-cased tool name to its canonical form for normalised output. + const canonical = new Map(sorted.map((n) => [n.toLowerCase(), n])); + const re = new RegExp(`\\b(?:${sorted.map(escapeRegExp).join('|')})\\b`, 'gi'); + return escaped.replace(re, (match) => { + const name = canonical.get(match.toLowerCase()) ?? match; + return `${escapeHtml(name)}`; + }); +} diff --git a/dashboard/src/lib/modalAction.svelte.ts b/dashboard/src/lib/modalAction.svelte.ts new file mode 100644 index 000000000..d823ccdbb --- /dev/null +++ b/dashboard/src/lib/modalAction.svelte.ts @@ -0,0 +1,37 @@ +import type { MutationResult } from './api/mutation'; + +/** + * Owns the busy/error lifecycle shared by every mutating modal. Callers pass a function + * returning a normalized MutationResult (wrap raw endpoints with `runMutation`), plus an + * onSuccess callback (typically `onclose`). Must be a `.svelte.ts` module so it can hold $state. + */ +export function createModalAction() { + let busy = $state(false); + let error = $state(''); + return { + get busy() { + return busy; + }, + get error() { + return error; + }, + clearError() { + error = ''; + }, + async run( + fn: () => Promise>, + onSuccess: () => void, + ): Promise> { + busy = true; + error = ''; + const res = await fn(); + busy = false; + if (!res.ok) { + error = res.message ?? ''; + return res; + } + onSuccess(); + return res; + }, + }; +} diff --git a/dashboard/src/lib/news.ts b/dashboard/src/lib/news.ts new file mode 100644 index 000000000..28283bcb6 --- /dev/null +++ b/dashboard/src/lib/news.ts @@ -0,0 +1,4 @@ +/** Sort news entries by YYYYMMDD id, newest first (matches the legacy dashboard). */ +export function sortNewsEntries(news: Record): Array<[string, string]> { + return Object.entries(news).sort((a, b) => b[0].localeCompare(a[0])); +} diff --git a/dashboard/src/lib/pollers.ts b/dashboard/src/lib/pollers.ts new file mode 100644 index 000000000..1d2b908d5 --- /dev/null +++ b/dashboard/src/lib/pollers.ts @@ -0,0 +1,14 @@ +export type View = 'overview' | 'logs' | 'stats'; +export type PollerName = 'config' | 'queued' | 'last' | 'logs'; + +/** Which pollers should be running for a given view. Pure so it is unit-testable. */ +export function pollersForView(view: View): PollerName[] { + switch (view) { + case 'overview': + return ['config', 'queued', 'last']; + case 'logs': + return ['logs']; + case 'stats': + return []; + } +} diff --git a/dashboard/src/lib/polling.ts b/dashboard/src/lib/polling.ts new file mode 100644 index 000000000..be043605e --- /dev/null +++ b/dashboard/src/lib/polling.ts @@ -0,0 +1,36 @@ +export interface Poller { + start(): void; + stop(): void; +} + +export function createPoller(fn: () => Promise | void, intervalMs: number): Poller { + let timer: ReturnType | null = null; + let inFlight = false; + + const tick = async () => { + if (inFlight) return; + inFlight = true; + try { + await fn(); + } finally { + inFlight = false; + } + }; + + return { + start() { + if (timer) return; + void tick(); + timer = setInterval(() => void tick(), intervalMs); + }, + stop() { + if (timer) { + clearInterval(timer); + timer = null; + } + // Allow a fresh start() to poll immediately even if a previous call is still + // in flight; that orphaned call's (void) result is simply discarded. + inFlight = false; + }, + }; +} diff --git a/dashboard/src/lib/stores/config.svelte.ts b/dashboard/src/lib/stores/config.svelte.ts new file mode 100644 index 000000000..fd84ae20f --- /dev/null +++ b/dashboard/src/lib/stores/config.svelte.ts @@ -0,0 +1,23 @@ +import { fetchConfigOverview } from '$lib/api/endpoints'; +import type { ResponseConfigOverview } from '$lib/api/types'; + +export function createConfigStore() { + let data = $state(null); + let lastJson = ''; + + return { + get data() { + return data; + }, + async poll() { + const next = await fetchConfigOverview(); + const json = JSON.stringify(next); + if (json !== lastJson) { + data = next; + lastJson = json; + } + }, + }; +} + +export const config = createConfigStore(); diff --git a/dashboard/src/lib/stores/executions.svelte.ts b/dashboard/src/lib/stores/executions.svelte.ts new file mode 100644 index 000000000..352cf9ad9 --- /dev/null +++ b/dashboard/src/lib/stores/executions.svelte.ts @@ -0,0 +1,65 @@ +import { fetchQueuedExecutions, fetchLastExecution, cancelExecution } from '$lib/api/endpoints'; +import { runMutation } from '$lib/api/mutation'; +import type { QueuedExecution } from '$lib/api/types'; + +export function createExecutionsStore() { + let queued = $state([]); + let last = $state(null); + // Client-side list of cancelled/abandoned tasks for the Cancelled Executions panel + // (legacy behaviour: there is no backend endpoint for this). + let cancelled = $state([]); + let cancelError = $state(''); + // task_ids with an in-flight cancel, to dedupe concurrent double-clicks on a + // queued item's × (which would otherwise fire two requests for the same task). + const inFlight = new Set(); + + return { + get queued() { + return queued; + }, + get last() { + return last; + }, + get cancelled() { + return cancelled; + }, + get cancelError() { + return cancelError; + }, + clearCancelError() { + cancelError = ''; + }, + async pollQueued() { + queued = (await fetchQueuedExecutions()).queued_executions; + }, + async pollLast() { + // Only surface user-facing (logged) executions. The backend runs internal tasks + // (e.g. _get_config_overview every second) with logged=False; without this filter + // the "Last Execution" panel would almost always show an internal task. Matches legacy. + const latest = (await fetchLastExecution()).last_execution; + last = latest && latest.logged ? latest : null; + }, + async cancel(execution: QueuedExecution) { + if (inFlight.has(execution.task_id)) return { ok: false }; + inFlight.add(execution.task_id); + cancelError = ''; + try { + const res = await runMutation(() => cancelExecution(execution.task_id)); + if (!res.ok) { + cancelError = res.message ?? 'Failed to cancel execution'; + return res; + } + // Dedupe: a queued item can be cancelled again before the next poll removes + // it, so guard against recording the same task twice (duplicate {#each} key). + if (!cancelled.some((c) => c.task_id === execution.task_id)) { + cancelled = [...cancelled, execution]; + } + return res; + } finally { + inFlight.delete(execution.task_id); + } + }, + }; +} + +export const executions = createExecutionsStore(); diff --git a/dashboard/src/lib/stores/logs.svelte.ts b/dashboard/src/lib/stores/logs.svelte.ts new file mode 100644 index 000000000..aa78470b9 --- /dev/null +++ b/dashboard/src/lib/stores/logs.svelte.ts @@ -0,0 +1,29 @@ +import { fetchLogMessages, clearLogs as apiClearLogs } from '$lib/api/endpoints'; + +export function createLogsStore() { + let lines = $state([]); + let nextIdx = $state(0); + let activeProject = $state(null); + + return { + get lines() { + return lines; + }, + get activeProject() { + return activeProject; + }, + async poll() { + const res = await fetchLogMessages(nextIdx); + if (res.messages.length) lines = [...lines, ...res.messages]; + nextIdx = res.max_idx; + activeProject = res.active_project; + }, + async clear() { + await apiClearLogs(); + lines = []; + nextIdx = 0; + }, + }; +} + +export const logs = createLogsStore(); diff --git a/dashboard/src/lib/stores/modal.svelte.ts b/dashboard/src/lib/stores/modal.svelte.ts new file mode 100644 index 000000000..3eafac522 --- /dev/null +++ b/dashboard/src/lib/stores/modal.svelte.ts @@ -0,0 +1,28 @@ +import type { QueuedExecution } from '$lib/api/types'; + +export type ModalState = + | { kind: 'shutdown' } + | { kind: 'cancelExecution'; execution: QueuedExecution } + | { kind: 'addLanguage' } + | { kind: 'removeLanguage'; language: string } + | { kind: 'editMemory'; name: string } + | { kind: 'deleteMemory'; name: string } + | { kind: 'createMemory' } + | { kind: 'editSerenaConfig' }; + +export function createModalStore() { + let active = $state(null); + return { + get active() { + return active; + }, + open(s: ModalState) { + active = s; + }, + close() { + active = null; + }, + }; +} + +export const modal = createModalStore(); diff --git a/dashboard/src/lib/stores/stats.svelte.ts b/dashboard/src/lib/stores/stats.svelte.ts new file mode 100644 index 000000000..276133e73 --- /dev/null +++ b/dashboard/src/lib/stores/stats.svelte.ts @@ -0,0 +1,26 @@ +import { fetchToolStats, clearToolStats, fetchEstimatorName } from '$lib/api/endpoints'; +import type { ToolStats } from '$lib/api/types'; + +export function createStatsStore() { + let stats = $state({}); + let estimator = $state('unknown'); + + return { + get stats() { + return stats; + }, + get estimator() { + return estimator; + }, + async refresh() { + stats = (await fetchToolStats()).stats; + estimator = (await fetchEstimatorName()).token_count_estimator_name; + }, + async clear() { + await clearToolStats(); + stats = {}; + }, + }; +} + +export const stats = createStatsStore(); diff --git a/dashboard/src/lib/stores/theme.svelte.ts b/dashboard/src/lib/stores/theme.svelte.ts new file mode 100644 index 000000000..66ae4c3fc --- /dev/null +++ b/dashboard/src/lib/stores/theme.svelte.ts @@ -0,0 +1,29 @@ +export type Theme = 'light' | 'dark'; +const KEY = 'serena-dashboard-theme'; + +export function createThemeStore() { + let current = $state('light'); + + function apply(t: Theme) { + current = t; + document.documentElement.setAttribute('data-theme', t); + } + + return { + get current() { + return current; + }, + init() { + const stored = localStorage.getItem(KEY) as Theme | null; + const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)').matches; + apply(stored ?? (prefersDark ? 'dark' : 'light')); + }, + toggle() { + const next: Theme = current === 'dark' ? 'light' : 'dark'; + apply(next); + localStorage.setItem(KEY, next); + }, + }; +} + +export const theme = createThemeStore(); diff --git a/dashboard/src/lib/title.ts b/dashboard/src/lib/title.ts new file mode 100644 index 000000000..3a78fb8b6 --- /dev/null +++ b/dashboard/src/lib/title.ts @@ -0,0 +1,3 @@ +export function pageTitle(project: string | null | undefined): string { + return project ? `${project} – Serena Dashboard` : 'Serena Dashboard'; +} diff --git a/dashboard/src/lib/validation.ts b/dashboard/src/lib/validation.ts new file mode 100644 index 000000000..f02b28f3a --- /dev/null +++ b/dashboard/src/lib/validation.ts @@ -0,0 +1,3 @@ +export function isValidMemoryName(name: string): boolean { + return /^[A-Za-z0-9_]+(\/[A-Za-z0-9_]+)*$/.test(name); +} diff --git a/dashboard/src/main.ts b/dashboard/src/main.ts new file mode 100644 index 000000000..30682f3b6 --- /dev/null +++ b/dashboard/src/main.ts @@ -0,0 +1,7 @@ +import { mount } from 'svelte'; +import './styles/tokens.css'; +import './styles/global.css'; +import App from './App.svelte'; + +const app = mount(App, { target: document.getElementById('app')! }); +export default app; diff --git a/dashboard/src/styles/global.css b/dashboard/src/styles/global.css new file mode 100644 index 000000000..a4c865e13 --- /dev/null +++ b/dashboard/src/styles/global.css @@ -0,0 +1,85 @@ +* { + box-sizing: border-box; +} + +html { + scrollbar-gutter: stable; +} + +body { + margin: 0; + font-family: var(--font-sans); + font-size: 14px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background: var(--bg); + color: var(--text-primary); + transition: + background-color 0.3s ease, + color 0.3s ease; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 600; + letter-spacing: -0.005em; +} + +code, +pre, +.mono { + font-family: var(--font-mono); +} + +a { + color: inherit; + text-decoration: none; +} + +input:focus-visible, +textarea:focus-visible { + outline: none; + border-color: var(--accent); + box-shadow: var(--focus-ring); +} + +.modal-actions { + display: flex; + gap: var(--space-3); + justify-content: flex-end; + margin-top: var(--space-4); +} + +.modal-info { + color: var(--text-secondary); +} + +.modal-hint { + color: var(--text-muted); + font-size: 13px; +} + +.modal-input { + width: 100%; + padding: var(--space-2); + border: 1px solid var(--border-strong); + border-radius: var(--radius-sm); + background: var(--bg-card); + color: var(--text-primary); +} + +.modal-textarea { + width: 100%; + font-family: var(--font-mono); + font-size: 13px; + background: var(--bg-card); + color: var(--text-primary); + border: 1px solid var(--border-strong); + border-radius: var(--radius-sm); + padding: var(--space-3); +} diff --git a/dashboard/src/styles/tokens.css b/dashboard/src/styles/tokens.css new file mode 100644 index 000000000..948970446 --- /dev/null +++ b/dashboard/src/styles/tokens.css @@ -0,0 +1,66 @@ +:root { + --bg: #f5f5f5; + --bg-card: #ffffff; + --bg-elevated: #ffffff; + --bg-secondary-btn: #f0f2f5; + --text-primary: #1f2328; + --text-secondary: #3f4754; + --text-muted: #6a737d; + --border: #e3e6ea; + --border-strong: #d0d7de; + --accent: #eaa45d; + --accent-hover: #dca662; + --chart-2: #6aa3d8; + --chart-3: #7fb77e; + --chart-4: #d88c8c; + --chart-5: #b39ddb; + --chart-6: #e0a458; + --text-on-accent: #ffffff; + --btn-disabled: #adb5bd; + --tool-highlight: #fff3bf; + --tool-highlight-text: #1f2328; + --btn-secondary-hover: #e3e6ea; + --stats-header: #f0f2f5; + --success: #22c55e; + --log-debug: #8b95a1; + --log-info: #1f2328; + --log-warning: #d97706; + --log-error: #dc2626; + --radius: 6px; + --radius-sm: 4px; + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-6: 24px; + --space-8: 32px; + --max-width: 1600px; + --font-sans: + 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + --font-mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; + --shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 1px 3px rgba(15, 23, 42, 0.06); + --shadow-elevated: 0 4px 12px rgba(15, 23, 42, 0.08); + --focus-ring: 0 0 0 3px rgba(234, 164, 93, 0.18); +} + +[data-theme='dark'] { + --bg: #1a1a1a; + --bg-card: #2d2d2d; + --bg-elevated: #262b32; + --bg-secondary-btn: #2d333b; + --text-primary: #e6edf3; + --text-secondary: #c9d1d9; + --text-muted: #8b95a1; + --border: #2d333b; + --border-strong: #3d444d; + --btn-disabled: #4a5159; + --tool-highlight: #f6c948; + --tool-highlight-text: #15181c; + --btn-secondary-hover: #3d444d; + --stats-header: #262b32; + --log-info: #e6edf3; + --log-warning: #f59e0b; + --log-error: #f87171; + --shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 1px 3px rgba(0, 0, 0, 0.35); + --shadow-elevated: 0 4px 12px rgba(0, 0, 0, 0.45); +} diff --git a/dashboard/src/types/frappe-charts.d.ts b/dashboard/src/types/frappe-charts.d.ts new file mode 100644 index 000000000..44bc0fb9b --- /dev/null +++ b/dashboard/src/types/frappe-charts.d.ts @@ -0,0 +1,7 @@ +declare module 'frappe-charts' { + export class Chart { + constructor(parent: HTMLElement | string, options: Record); + update(data: { labels: string[]; datasets: Array<{ name?: string; values: number[] }> }): void; + destroy(): void; + } +} diff --git a/dashboard/svelte.config.js b/dashboard/svelte.config.js new file mode 100644 index 000000000..a0767f23f --- /dev/null +++ b/dashboard/svelte.config.js @@ -0,0 +1,6 @@ +import process from 'node:process'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; +// Disable CSS preprocessing in test/check environments to avoid Vite 6 +// PartialEnvironment errors when a fully resolved config isn't available. +const isTest = process.env.VITEST === 'true' || process.env.NODE_ENV === 'test'; +export default { preprocess: vitePreprocess({ style: !isTest }) }; diff --git a/dashboard/tests/add-language-modal.test.ts b/dashboard/tests/add-language-modal.test.ts new file mode 100644 index 000000000..ea042e9fb --- /dev/null +++ b/dashboard/tests/add-language-modal.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, fireEvent, screen } from '@testing-library/svelte'; +import AddLanguageModal from '../src/components/modals/AddLanguageModal.svelte'; +import { stubFetchRoutes, errBody } from './helpers'; + +describe('AddLanguageModal error handling', () => { + it('shows the error inline and stays open when add fails', async () => { + stubFetchRoutes( + { '/get_available_languages': { languages: ['go'] } }, + errBody('unknown language'), + ); + const onclose = vi.fn(); + render(AddLanguageModal, { props: { projectName: 'serena', onclose } }); + const input = screen.getByRole('textbox'); + await fireEvent.focus(input); + await fireEvent.click(await screen.findByText('go')); + await fireEvent.click(screen.getByRole('button', { name: 'Add Language' })); + expect(await screen.findByText('unknown language')).toBeInTheDocument(); + expect(onclose).not.toHaveBeenCalled(); + }); +}); diff --git a/dashboard/tests/banners.test.ts b/dashboard/tests/banners.test.ts new file mode 100644 index 000000000..690a80a62 --- /dev/null +++ b/dashboard/tests/banners.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from 'vitest'; +import { selectBanners, pickVariant } from '../src/lib/banners'; + +const manifest = { + platinum: [{ image: 'p-l.png', image_dark: 'p-d.png', link: 'https://a', alt: 'A' }], + gold: [ + { image: 'g-l.png', image_dark: 'g-d.png', link: 'https://b', alt: 'B' }, + { image: 'g2-l.png', link: 'https://c', alt: 'C' }, + ], +}; + +describe('banners', () => { + it('filters by target', () => { + expect(selectBanners(manifest, 'gold')).toHaveLength(2); + expect(selectBanners(manifest, 'platinum')).toHaveLength(1); + expect(selectBanners(manifest, 'platinum')[0].link).toBe('https://a'); + }); + + it('picks the variant for the theme', () => { + const b = selectBanners(manifest, 'platinum')[0]; + expect(pickVariant(b, 'dark')).toBe('p-d.png'); + expect(pickVariant(b, 'light')).toBe('p-l.png'); + }); + + it('falls back to the light image when no dark variant exists', () => { + const b = selectBanners(manifest, 'gold')[1]; + expect(pickVariant(b, 'dark')).toBe('g2-l.png'); + expect(pickVariant(b, 'light')).toBe('g2-l.png'); + }); + + it('tolerates a missing target array', () => { + expect(selectBanners({} as never, 'gold')).toEqual([]); + }); +}); diff --git a/dashboard/tests/charts.test.ts b/dashboard/tests/charts.test.ts new file mode 100644 index 000000000..5545dd29a --- /dev/null +++ b/dashboard/tests/charts.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from 'vitest'; +import { toPieData, toSingleSeriesBar } from '../src/lib/charts'; +import type { ToolStats } from '../src/lib/api/types'; + +const stats: ToolStats = { + a: { num_times_called: 2, input_tokens: 10, output_tokens: 5 }, + b: { num_times_called: 8, input_tokens: 40, output_tokens: 20 }, +}; + +describe('charts data', () => { + it('builds pie data for a chosen metric, sorted descending', () => { + const pie = toPieData(stats, 'num_times_called'); + expect(pie.labels).toEqual(['b', 'a']); + expect(pie.datasets[0].values).toEqual([8, 2]); + }); + it('builds a single-series bar for one token metric, sorted descending', () => { + const bar = toSingleSeriesBar(stats, 'output_tokens', 'Output Tokens'); + expect(bar.labels).toEqual(['b', 'a']); + expect(bar.datasets).toHaveLength(1); + expect(bar.datasets[0].name).toBe('Output Tokens'); + expect(bar.datasets[0].values).toEqual([20, 5]); + }); +}); diff --git a/dashboard/tests/client.test.ts b/dashboard/tests/client.test.ts new file mode 100644 index 000000000..39abf3aae --- /dev/null +++ b/dashboard/tests/client.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect, vi } from 'vitest'; +import { getJson, postJson, putJson, ApiError } from '../src/lib/api/client'; +import { stubFetchJson } from './helpers'; + +describe('client', () => { + it('getJson parses JSON on success', async () => { + stubFetchJson({ a: 1 }); + expect(await getJson<{ a: number }>('/x')).toEqual({ a: 1 }); + }); + + it('postJson sends a JSON body', async () => { + const f = vi.fn().mockResolvedValue(new Response('{}', { status: 200 })); + vi.stubGlobal('fetch', f); + await postJson('/y', { name: 'go' }); + expect(f).toHaveBeenCalledWith( + '/y', + expect.objectContaining({ + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name: 'go' }), + }), + ); + }); + + it('throws ApiError on non-2xx', async () => { + stubFetchJson('nope', 500); + await expect(getJson('/z')).rejects.toBeInstanceOf(ApiError); + }); + + it('putJson issues a PUT', async () => { + const f = vi.fn().mockResolvedValue(new Response('{}', { status: 200 })); + vi.stubGlobal('fetch', f); + await putJson('/shutdown'); + expect(f).toHaveBeenCalledWith('/shutdown', expect.objectContaining({ method: 'PUT' })); + }); +}); diff --git a/dashboard/tests/combobox.test.ts b/dashboard/tests/combobox.test.ts new file mode 100644 index 000000000..6d9335470 --- /dev/null +++ b/dashboard/tests/combobox.test.ts @@ -0,0 +1,14 @@ +import { describe, it, expect } from 'vitest'; +import { render, fireEvent } from '@testing-library/svelte'; +import Combobox from '../src/components/common/Combobox.svelte'; + +describe('Combobox', () => { + it('filters options by typed text', async () => { + const { getByRole, queryByText, getByText } = render(Combobox, { + props: { options: ['python', 'typescript', 'rust'], value: '', onselect: () => {} }, + }); + await fireEvent.input(getByRole('textbox'), { target: { value: 'ty' } }); + expect(getByText('typescript')).toBeInTheDocument(); + expect(queryByText('rust')).toBeNull(); + }); +}); diff --git a/dashboard/tests/config-card.test.ts b/dashboard/tests/config-card.test.ts new file mode 100644 index 000000000..5eefe38d3 --- /dev/null +++ b/dashboard/tests/config-card.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import ConfigCard from '../src/components/overview/ConfigCard.svelte'; +import type { ResponseConfigOverview } from '../src/lib/api/types'; + +function makeConfig(over: Partial = {}): ResponseConfigOverview { + return { + active_project: { name: 'serena', language: 'Python', path: '/x' }, + context: { name: 'claude-code', description: '', path: '/ctx' }, + modes: [{ name: 'editing', path: '/m1' }], + active_tools: ['find_symbol'], + tool_stats_summary: {}, + registered_projects: [], + available_tools: [], + available_modes: [], + available_contexts: [], + available_memories: [], + jetbrains_mode: false, + languages: ['python'], + encoding: 'utf-8', + current_client: 'claude', + serena_version: '1.5.4', + ...over, + }; +} + +const cbs = { + onaddlanguage: vi.fn(), + onremovelanguage: vi.fn(), + oneditconfig: vi.fn(), + onopenmemory: vi.fn(), + oncreatememory: vi.fn(), + ondeletememory: vi.fn(), +}; + +describe('ConfigCard', () => { + it('does not render a Client field', () => { + render(ConfigCard, { props: { data: makeConfig(), ...cbs } }); + expect(screen.queryByText('Client')).toBeNull(); + }); + + it('shows the JetBrains backend notice and hides Add Language in jetbrains mode', () => { + render(ConfigCard, { props: { data: makeConfig({ jetbrains_mode: true }), ...cbs } }); + expect(screen.getByText('Using JetBrains backend')).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Add Language' })).toBeNull(); + }); + + it('shows Add Language when not in jetbrains mode', () => { + render(ConfigCard, { props: { data: makeConfig(), ...cbs } }); + expect(screen.getByRole('button', { name: 'Add Language' })).toBeInTheDocument(); + }); +}); diff --git a/dashboard/tests/config-store.test.ts b/dashboard/tests/config-store.test.ts new file mode 100644 index 000000000..ca7853331 --- /dev/null +++ b/dashboard/tests/config-store.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest'; +import { createConfigStore } from '../src/lib/stores/config.svelte'; +import { stubFetchJson } from './helpers'; + +const overview = { serena_version: '1.0', active_project: null, languages: [] }; + +describe('config store', () => { + it('keeps the same reference when the polled body is unchanged', async () => { + stubFetchJson(overview); + const store = createConfigStore(); + await store.poll(); + const first = store.data; + await store.poll(); + expect(store.data).toBe(first); // dedup via JSON compare → no reassignment + }); + + it('updates data when the polled body changes', async () => { + const fetchMock = stubFetchJson(overview); + const store = createConfigStore(); + await store.poll(); + const first = store.data; + fetchMock.mockResolvedValue( + new Response(JSON.stringify({ ...overview, serena_version: '2.0' }), { status: 200 }), + ); + await store.poll(); + expect(store.data).not.toBe(first); + expect(store.data?.serena_version).toBe('2.0'); + }); +}); diff --git a/dashboard/tests/confirmDiscard.test.ts b/dashboard/tests/confirmDiscard.test.ts new file mode 100644 index 000000000..12c87be79 --- /dev/null +++ b/dashboard/tests/confirmDiscard.test.ts @@ -0,0 +1,19 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { confirmDiscard } from '../src/lib/confirmDiscard'; + +afterEach(() => vi.restoreAllMocks()); + +describe('confirmDiscard', () => { + it('returns true without prompting when not dirty', () => { + const spy = vi.spyOn(window, 'confirm'); + expect(confirmDiscard(false)).toBe(true); + expect(spy).not.toHaveBeenCalled(); + }); + + it('defers to window.confirm when dirty', () => { + vi.spyOn(window, 'confirm').mockReturnValue(true); + expect(confirmDiscard(true)).toBe(true); + vi.spyOn(window, 'confirm').mockReturnValue(false); + expect(confirmDiscard(true)).toBe(false); + }); +}); diff --git a/dashboard/tests/create-memory-modal.test.ts b/dashboard/tests/create-memory-modal.test.ts new file mode 100644 index 000000000..da1ed26e6 --- /dev/null +++ b/dashboard/tests/create-memory-modal.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, fireEvent, screen } from '@testing-library/svelte'; +import CreateMemoryModal from '../src/components/modals/CreateMemoryModal.svelte'; +import { stubFetchJson, errBody } from './helpers'; + +describe('CreateMemoryModal', () => { + it('disables Create until the name is valid', async () => { + stubFetchJson(errBody('nope')); + render(CreateMemoryModal, { + props: { projectName: 'serena', onclose: vi.fn(), oncreated: vi.fn() }, + }); + const create = screen.getByRole('button', { name: 'Create' }); + expect(create).toBeDisabled(); + await fireEvent.input(screen.getByRole('textbox'), { target: { value: 'valid_name' } }); + expect(create).not.toBeDisabled(); + }); + + it('shows the error inline and does not signal creation when save fails', async () => { + stubFetchJson(errBody('already exists')); + const oncreated = vi.fn(); + render(CreateMemoryModal, { props: { projectName: 'serena', onclose: vi.fn(), oncreated } }); + await fireEvent.input(screen.getByRole('textbox'), { target: { value: 'dup' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Create' })); + expect(await screen.findByText('already exists')).toBeInTheDocument(); + expect(oncreated).not.toHaveBeenCalled(); + }); +}); diff --git a/dashboard/tests/delete-memory-modal.test.ts b/dashboard/tests/delete-memory-modal.test.ts new file mode 100644 index 000000000..d2aafb07d --- /dev/null +++ b/dashboard/tests/delete-memory-modal.test.ts @@ -0,0 +1,15 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, fireEvent, screen } from '@testing-library/svelte'; +import DeleteMemoryModal from '../src/components/modals/DeleteMemoryModal.svelte'; +import { stubFetchJson, errBody } from './helpers'; + +describe('DeleteMemoryModal error handling', () => { + it('shows the error and stays open when delete fails', async () => { + stubFetchJson(errBody('cannot delete')); + const onclose = vi.fn(); + render(DeleteMemoryModal, { props: { name: 'core', onclose } }); + await fireEvent.click(screen.getByRole('button', { name: 'OK' })); + expect(await screen.findByText('cannot delete')).toBeInTheDocument(); + expect(onclose).not.toHaveBeenCalled(); + }); +}); diff --git a/dashboard/tests/edit-memory-modal.test.ts b/dashboard/tests/edit-memory-modal.test.ts new file mode 100644 index 000000000..f29eb973f --- /dev/null +++ b/dashboard/tests/edit-memory-modal.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, fireEvent, screen, waitFor } from '@testing-library/svelte'; +import EditMemoryModal from '../src/components/modals/EditMemoryModal.svelte'; +import { stubFetchJson } from './helpers'; + +describe('EditMemoryModal', () => { + it('prompts before discarding unsaved edits and aborts close when declined', async () => { + stubFetchJson({ content: 'orig', memory_name: 'core' }); + const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); + const onclose = vi.fn(); + render(EditMemoryModal, { props: { name: 'core', onclose } }); + // Wait for the async onMount fetch to complete so the textarea has the loaded + // content ('orig') before we simulate editing. findByRole returns as soon as + // the element exists, but the value is only set after the fetch resolves. + const textarea = await screen.findByRole('textbox'); + await waitFor(() => { + if ((textarea as HTMLTextAreaElement).value !== 'orig') throw new Error('not loaded yet'); + }); + await fireEvent.input(textarea, { target: { value: 'changed' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); + expect(confirmSpy).toHaveBeenCalled(); + expect(onclose).not.toHaveBeenCalled(); + }); +}); diff --git a/dashboard/tests/edit-serena-config-modal.test.ts b/dashboard/tests/edit-serena-config-modal.test.ts new file mode 100644 index 000000000..fc545425a --- /dev/null +++ b/dashboard/tests/edit-serena-config-modal.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, fireEvent, screen, waitFor } from '@testing-library/svelte'; +import EditSerenaConfigModal from '../src/components/modals/EditSerenaConfigModal.svelte'; +import { stubFetchJson } from './helpers'; + +describe('EditSerenaConfigModal', () => { + it('prompts before discarding unsaved edits and aborts close when declined', async () => { + stubFetchJson({ content: 'yaml: 1' }); + const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); + const onclose = vi.fn(); + render(EditSerenaConfigModal, { props: { onclose } }); + const textarea = await screen.findByRole('textbox'); + // Wait for the async onMount fetch to complete so the textarea has the loaded + // content ('yaml: 1') before we simulate editing. + await waitFor(() => { + if ((textarea as HTMLTextAreaElement).value !== 'yaml: 1') throw new Error('not loaded yet'); + }); + await fireEvent.input(textarea, { target: { value: 'yaml: 2' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); + expect(confirmSpy).toHaveBeenCalled(); + expect(onclose).not.toHaveBeenCalled(); + }); +}); diff --git a/dashboard/tests/endpoints.test.ts b/dashboard/tests/endpoints.test.ts new file mode 100644 index 000000000..3c74b1e15 --- /dev/null +++ b/dashboard/tests/endpoints.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import * as api from '../src/lib/api/endpoints'; + +let fetchMock: ReturnType; +beforeEach(() => { + fetchMock = vi.fn().mockResolvedValue(new Response('{}', { status: 200 })); + vi.stubGlobal('fetch', fetchMock); +}); + +describe('endpoints', () => { + it('fetchConfigOverview GETs /get_config_overview', async () => { + await api.fetchConfigOverview(); + expect(fetchMock).toHaveBeenCalledWith('/get_config_overview'); + }); + it('fetchLogMessages POSTs /get_log_messages with start_idx', async () => { + await api.fetchLogMessages(5); + expect(fetchMock).toHaveBeenCalledWith( + '/get_log_messages', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ start_idx: 5 }), + }), + ); + }); + it('shutdown PUTs /shutdown', async () => { + await api.shutdown(); + expect(fetchMock).toHaveBeenCalledWith('/shutdown', expect.objectContaining({ method: 'PUT' })); + }); +}); diff --git a/dashboard/tests/executions-queue.test.ts b/dashboard/tests/executions-queue.test.ts new file mode 100644 index 000000000..f22e78103 --- /dev/null +++ b/dashboard/tests/executions-queue.test.ts @@ -0,0 +1,48 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, fireEvent, screen, getAllByRole } from '@testing-library/svelte'; +import ExecutionsQueue from '../src/components/overview/ExecutionsQueue.svelte'; +import { exec } from './helpers'; + +describe('ExecutionsQueue', () => { + it('renders a cancel button for a queued (non-running) item and emits the execution', async () => { + const item = exec({ task_id: 9, is_running: false, name: 'queued-task' }); + const oncancelexecution = vi.fn(); + render(ExecutionsQueue, { props: { items: [item], cancelError: '', oncancelexecution } }); + await fireEvent.click(screen.getByTestId('cancel-btn')); + expect(oncancelexecution).toHaveBeenCalledWith(item); + }); + + it('shows a cancel button for every logged item', () => { + const { container } = render(ExecutionsQueue, { + props: { + items: [ + exec({ task_id: 1, is_running: true, name: 'run' }), + exec({ task_id: 2, name: 'queued' }), + ], + oncancelexecution: vi.fn(), + }, + }); + expect(getAllByRole(container, 'button').length).toBe(2); + }); + + it('hides unlogged background tasks', () => { + const { container, queryByText } = render(ExecutionsQueue, { + props: { + items: [ + exec({ task_id: 1, is_running: true, name: 'logged-task' }), + exec({ task_id: 2, name: '_get_config_overview', logged: false }), + ], + oncancelexecution: vi.fn(), + }, + }); + expect(container.querySelectorAll('.execution-item').length).toBe(1); + expect(queryByText('_get_config_overview')).toBeNull(); + }); + + it('shows the cancel error when set', () => { + render(ExecutionsQueue, { + props: { items: [exec({})], cancelError: 'too late', oncancelexecution: vi.fn() }, + }); + expect(screen.getByText('too late')).toBeInTheDocument(); + }); +}); diff --git a/dashboard/tests/executions-store.test.ts b/dashboard/tests/executions-store.test.ts new file mode 100644 index 000000000..576ce183b --- /dev/null +++ b/dashboard/tests/executions-store.test.ts @@ -0,0 +1,66 @@ +import { describe, it, expect } from 'vitest'; +import { createExecutionsStore } from '../src/lib/stores/executions.svelte'; +import { exec, okBody, errBody, stubFetchJson } from './helpers'; + +describe('executions store: cancel', () => { + it('records a cancelled execution on success', async () => { + stubFetchJson(okBody({ was_cancelled: true, message: '' })); + const store = createExecutionsStore(); + const r = await store.cancel(exec({ task_id: 7, name: 't', is_running: true })); + expect(r.ok).toBe(true); + expect(store.cancelled.map((c) => c.task_id)).toEqual([7]); + expect(store.cancelError).toBe(''); + }); + + it('sets cancelError and does not record on failure', async () => { + stubFetchJson(errBody('too late')); + const store = createExecutionsStore(); + const r = await store.cancel(exec({ task_id: 8 })); + expect(r.ok).toBe(false); + expect(store.cancelError).toBe('too late'); + expect(store.cancelled).toEqual([]); + }); + + it('does not record the same cancelled task twice', async () => { + stubFetchJson(okBody({ was_cancelled: true, message: '' })); + const store = createExecutionsStore(); + const e = exec({ task_id: 7 }); + await store.cancel(e); + await store.cancel(e); + expect(store.cancelled.map((c) => c.task_id)).toEqual([7]); + }); + + it('clearCancelError resets the error', async () => { + stubFetchJson(errBody('too late')); + const store = createExecutionsStore(); + await store.cancel(exec({ task_id: 8 })); + expect(store.cancelError).toBe('too late'); + store.clearCancelError(); + expect(store.cancelError).toBe(''); + }); +}); + +describe('executions store: last execution', () => { + it('keeps the last execution when it is logged', async () => { + const last = exec({ name: 'real-task', logged: true }); + stubFetchJson({ last_execution: last, status: 'success' }); + const store = createExecutionsStore(); + await store.pollLast(); + expect(store.last?.name).toBe('real-task'); + }); + + it('hides the last execution when it is an unlogged internal task', async () => { + const last = exec({ name: '_get_config_overview', logged: false }); + stubFetchJson({ last_execution: last, status: 'success' }); + const store = createExecutionsStore(); + await store.pollLast(); + expect(store.last).toBeNull(); + }); + + it('is null when the backend reports no last execution', async () => { + stubFetchJson({ last_execution: null, status: 'success' }); + const store = createExecutionsStore(); + await store.pollLast(); + expect(store.last).toBeNull(); + }); +}); diff --git a/dashboard/tests/format.test.ts b/dashboard/tests/format.test.ts new file mode 100644 index 000000000..a43a8817c --- /dev/null +++ b/dashboard/tests/format.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from 'vitest'; +import { detectLevel, escapeHtml, highlightTools } from '../src/lib/format'; + +describe('format', () => { + it('detects log level from the line prefix', () => { + expect(detectLevel('INFO 2026-01-01 something')).toBe('info'); + expect(detectLevel('WARNING 2026-01-01 watch out')).toBe('warning'); + expect(detectLevel('ERROR 2026-01-01 boom')).toBe('error'); + expect(detectLevel('DEBUG 2026-01-01 noise')).toBe('debug'); + expect(detectLevel('no level here')).toBe('info'); + }); + it('does not recolor a line just because a level word appears mid-message', () => { + expect(detectLevel('INFO handled an ERROR gracefully')).toBe('info'); + }); + it('highlights tool names case-insensitively', () => { + const html = highlightTools('called Find_Symbol now', ['find_symbol']); + expect(html).toContain('find_symbol'); + }); + it('escapes html including single quotes', () => { + expect(escapeHtml(`&"'`)).toBe('<b>&"''); + }); + it('escapes HTML in the log text before highlighting', () => { + const html = highlightTools(' find_symbol', ['find_symbol']); + expect(html).toContain('<script>'); + expect(html).not.toContain(' +``` + +Change the `` open tag (line 12) to `` and the OK button (line 16) to: + +```svelte + +``` + +- [ ] **Step 4: Update `DeleteMemoryModal.svelte`** + +Replace the ` +``` + +Change the `` open tag (line 12) to `` and the OK button (line 16) to: + +```svelte + +``` + +- [ ] **Step 5: Update `CreateMemoryModal.svelte`** + +Add imports after line 5: + +```svelte + import Spinner from '../common/Spinner.svelte'; + import { runMutation } from '$lib/api/mutation'; +``` + +Add state after line 12 (`const valid = ...`): + +```svelte + let busy = $state(false); + let error = $state(''); +``` + +Replace the `create` function (lines 13-17) with: + +```svelte + async function create() { + if (!valid) return; + busy = true; + error = ''; + const res = await runMutation(() => saveMemory(name, '')); + busy = false; + if (!res.ok) { + error = res.message ?? 'Failed'; + return; + } + oncreated(name); + } +``` + +Change the `` open tag (line 20) to `` and the Create button (line 33) to: + +```svelte + +``` + +- [ ] **Step 6: Run test + typecheck** + +Run: `cd dashboard && npx vitest run tests/delete-memory-modal.test.ts && npm run check` +Expected: test PASS; 0 type errors. + +- [ ] **Step 7: Commit** + +```bash +git add dashboard/src/components/modals/RemoveLanguageModal.svelte dashboard/src/components/modals/DeleteMemoryModal.svelte dashboard/src/components/modals/CreateMemoryModal.svelte dashboard/tests/delete-memory-modal.test.ts +git commit -m "feat(dashboard): inline errors for remove-language/delete-memory/create-memory modals" +``` + +--- + +### Task 5: `EditMemoryModal` — inline errors + unsaved-changes guard + +**Files:** +- Modify: `dashboard/src/components/modals/EditMemoryModal.svelte` +- Test: `dashboard/tests/edit-memory-modal.test.ts` + +- [ ] **Step 1: Write the failing test** + +Create `dashboard/tests/edit-memory-modal.test.ts`: + +```ts +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { render, fireEvent, screen } from '@testing-library/svelte'; +import EditMemoryModal from '../src/components/modals/EditMemoryModal.svelte'; + +afterEach(() => vi.restoreAllMocks()); + +describe('EditMemoryModal', () => { + it('prompts before discarding unsaved edits and aborts close when declined', async () => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue( + new Response(JSON.stringify({ content: 'orig', memory_name: 'core' }), { status: 200 }), + ), + ); + const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); + const onclose = vi.fn(); + render(EditMemoryModal, { props: { name: 'core', onclose } }); + const textarea = await screen.findByRole('textbox'); + await fireEvent.input(textarea, { target: { value: 'changed' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); + expect(confirmSpy).toHaveBeenCalled(); + expect(onclose).not.toHaveBeenCalled(); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd dashboard && npx vitest run tests/edit-memory-modal.test.ts` +Expected: FAIL — no confirm prompt; `onclose` called. + +- [ ] **Step 3: Implement guard + errors** + +Replace the ` +``` + +Change the `` open tag (line 37) to `` and the Cancel button (line 65) to ``. Change the Save button (line 66) to: + +```svelte + +``` + +- [ ] **Step 4: Run test + typecheck** + +Run: `cd dashboard && npx vitest run tests/edit-memory-modal.test.ts && npm run check` +Expected: PASS; 0 type errors. + +- [ ] **Step 5: Commit** + +```bash +git add dashboard/src/components/modals/EditMemoryModal.svelte dashboard/tests/edit-memory-modal.test.ts +git commit -m "feat(dashboard): EditMemoryModal inline errors + unsaved-changes guard" +``` + +--- + +### Task 6: `EditSerenaConfigModal` — inline errors + unsaved-changes guard + +**Files:** +- Modify: `dashboard/src/components/modals/EditSerenaConfigModal.svelte` +- Test: `dashboard/tests/edit-serena-config-modal.test.ts` + +- [ ] **Step 1: Write the failing test** + +Create `dashboard/tests/edit-serena-config-modal.test.ts`: + +```ts +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { render, fireEvent, screen } from '@testing-library/svelte'; +import EditSerenaConfigModal from '../src/components/modals/EditSerenaConfigModal.svelte'; + +afterEach(() => vi.restoreAllMocks()); + +describe('EditSerenaConfigModal', () => { + it('prompts before discarding unsaved edits and aborts close when declined', async () => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue(new Response(JSON.stringify({ content: 'yaml: 1' }), { status: 200 })), + ); + const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); + const onclose = vi.fn(); + render(EditSerenaConfigModal, { props: { onclose } }); + const textarea = await screen.findByRole('textbox'); + await fireEvent.input(textarea, { target: { value: 'yaml: 2' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); + expect(confirmSpy).toHaveBeenCalled(); + expect(onclose).not.toHaveBeenCalled(); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd dashboard && npx vitest run tests/edit-serena-config-modal.test.ts` +Expected: FAIL — no prompt; `onclose` called. + +- [ ] **Step 3: Implement guard + errors** + +Replace the ` +``` + +Change the `` open tag (line 20) to ``. Change the Cancel button (line 26) to `` and the Save button (line 27) to ``. + +- [ ] **Step 4: Run test + typecheck** + +Run: `cd dashboard && npx vitest run tests/edit-serena-config-modal.test.ts && npm run check` +Expected: PASS; 0 type errors. + +- [ ] **Step 5: Commit** + +```bash +git add dashboard/src/components/modals/EditSerenaConfigModal.svelte dashboard/tests/edit-serena-config-modal.test.ts +git commit -m "feat(dashboard): EditSerenaConfigModal inline errors + unsaved-changes guard" +``` + +--- + +### Task 7: Restore full legacy execution-cancel UX + Cancelled Executions panel + +This is one cohesive change across the executions store, the cancel modal, modal state, ModalHost, App handler, OverviewPage, ExecutionsQueue, and a new CancelledExecutions panel. They are coupled by the handler signature (`oncancelexecution` now passes the full `QueuedExecution`), so they change together to keep the build green. + +**Files:** +- Modify: `dashboard/src/lib/stores/executions.svelte.ts` +- Modify: `dashboard/src/lib/stores/modal.svelte.ts` +- Modify: `dashboard/src/components/modals/CancelExecutionModal.svelte` +- Modify: `dashboard/src/components/modals/ModalHost.svelte` +- Modify: `dashboard/src/App.svelte` +- Modify: `dashboard/src/components/overview/OverviewPage.svelte` +- Modify: `dashboard/src/components/overview/ExecutionsQueue.svelte` +- Create: `dashboard/src/components/overview/CancelledExecutions.svelte` +- Test: `dashboard/tests/executions-store.test.ts` (extend), `dashboard/tests/executions-queue.test.ts` (new) + +- [ ] **Step 1: Write the failing store test (extend existing file)** + +Append to `dashboard/tests/executions-store.test.ts` (after the existing `describe` block, before EOF): + +```ts +describe('executions store: cancel', () => { + it('records a cancelled execution on success', async () => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue( + new Response(JSON.stringify({ status: 'success', was_cancelled: true, message: '' }), { + status: 200, + }), + ), + ); + const store = createExecutionsStore(); + const r = await store.cancel(exec({ task_id: 7, name: 't', is_running: true })); + expect(r.ok).toBe(true); + expect(store.cancelled.map((c) => c.task_id)).toEqual([7]); + expect(store.cancelError).toBe(''); + }); + + it('sets cancelError and does not record on failure', async () => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue( + new Response(JSON.stringify({ status: 'error', was_cancelled: false, message: 'too late' }), { + status: 200, + }), + ), + ); + const store = createExecutionsStore(); + const r = await store.cancel(exec({ task_id: 8 })); + expect(r.ok).toBe(false); + expect(store.cancelError).toBe('too late'); + expect(store.cancelled).toEqual([]); + }); +}); +``` + +- [ ] **Step 2: Write the failing queue component test** + +Create `dashboard/tests/executions-queue.test.ts`: + +```ts +import { describe, it, expect, vi } from 'vitest'; +import { render, fireEvent, screen } from '@testing-library/svelte'; +import ExecutionsQueue from '../src/components/overview/ExecutionsQueue.svelte'; +import type { QueuedExecution } from '../src/lib/api/types'; + +function exec(over: Partial): QueuedExecution { + return { task_id: 1, is_running: false, name: 'task', finished_successfully: true, logged: true, ...over }; +} + +describe('ExecutionsQueue', () => { + it('renders a cancel button for a queued (non-running) item and emits the execution', async () => { + const item = exec({ task_id: 9, is_running: false, name: 'queued-task' }); + const oncancelexecution = vi.fn(); + render(ExecutionsQueue, { props: { items: [item], cancelError: '', oncancelexecution } }); + await fireEvent.click(screen.getByTestId('cancel-btn')); + expect(oncancelexecution).toHaveBeenCalledWith(item); + }); + + it('shows the cancel error when set', () => { + render(ExecutionsQueue, { + props: { items: [exec({})], cancelError: 'too late', oncancelexecution: vi.fn() }, + }); + expect(screen.getByText('too late')).toBeInTheDocument(); + }); +}); +``` + +- [ ] **Step 3: Run both tests to verify they fail** + +Run: `cd dashboard && npx vitest run tests/executions-store.test.ts tests/executions-queue.test.ts` +Expected: FAIL — `store.cancel` signature/`cancelled`/`cancelError` missing; queue renders no cancel button for non-running items. + +- [ ] **Step 4: Update the executions store** + +Replace the entire `dashboard/src/lib/stores/executions.svelte.ts` with: + +```ts +import { fetchQueuedExecutions, fetchLastExecution, cancelExecution } from '$lib/api/endpoints'; +import { runMutation } from '$lib/api/mutation'; +import type { QueuedExecution } from '$lib/api/types'; + +export function createExecutionsStore() { + let queued = $state([]); + let last = $state(null); + // Client-side list of cancelled/abandoned tasks for the Cancelled Executions panel + // (legacy behaviour: there is no backend endpoint for this). + let cancelled = $state([]); + let cancelError = $state(''); + + return { + get queued() { + return queued; + }, + get last() { + return last; + }, + get cancelled() { + return cancelled; + }, + get cancelError() { + return cancelError; + }, + async pollQueued() { + queued = (await fetchQueuedExecutions()).queued_executions; + }, + async pollLast() { + // Only surface user-facing (logged) executions. The backend runs internal tasks + // (e.g. _get_config_overview every second) with logged=False; without this filter + // the "Last Execution" panel would almost always show an internal task. Matches legacy. + const latest = (await fetchLastExecution()).last_execution; + last = latest && latest.logged ? latest : null; + }, + async cancel(execution: QueuedExecution) { + cancelError = ''; + const res = await runMutation(() => cancelExecution(execution.task_id)); + if (!res.ok) { + cancelError = res.message ?? 'Failed to cancel execution'; + return res; + } + cancelled = [...cancelled, execution]; + return res; + }, + }; +} + +export const executions = createExecutionsStore(); +``` + +- [ ] **Step 5: Update the modal store state type** + +In `dashboard/src/lib/stores/modal.svelte.ts`, add the import at the top and change the `cancelExecution` variant: + +```ts +import type { QueuedExecution } from '$lib/api/types'; + +export type ModalState = + | { kind: 'shutdown' } + | { kind: 'cancelExecution'; execution: QueuedExecution } + | { kind: 'addLanguage' } + | { kind: 'removeLanguage'; language: string } + | { kind: 'editMemory'; name: string } + | { kind: 'deleteMemory'; name: string } + | { kind: 'createMemory' } + | { kind: 'editSerenaConfig' }; +``` + +(The `createModalStore`/`modal` export below is unchanged.) + +- [ ] **Step 6: Update `CancelExecutionModal.svelte`** + +Replace the whole file with: + +```svelte + + + + + + + + +``` + +- [ ] **Step 7: Update `ModalHost.svelte`** + +Change the `cancelExecution` branch (line 19) from `taskId={m.taskId}` to `execution={m.execution}`: + +```svelte + {:else if m.kind === 'cancelExecution'} +``` + +- [ ] **Step 8: Update `App.svelte` handler (branch running vs queued)** + +In `dashboard/src/App.svelte`, change the `oncancelexecution` prop passed to `OverviewPage` (line 65) to: + +```svelte + oncancelexecution={(ex) => + ex.is_running + ? modal.open({ kind: 'cancelExecution', execution: ex }) + : void executions.cancel(ex)} +``` + +(`executions` and `modal` are already imported in `App.svelte`.) + +- [ ] **Step 9: Update `ExecutionsQueue.svelte` (cancel for all items + cancelError)** + +Replace the whole file with: + +```svelte + + +{#if visible.length} +
+ {#each visible as ex (ex.task_id)} +
+ {#if ex.is_running}{/if} + {ex.name} + +
+ {/each} +
+{:else} +
No queued executions.
+{/if} +{#if cancelError}

{cancelError}

{/if} + + +``` + +- [ ] **Step 10: Create `CancelledExecutions.svelte`** + +Create `dashboard/src/components/overview/CancelledExecutions.svelte`: + +```svelte + + +
+ {#each items as ex (ex.task_id)} +
+ {ex.is_running ? '!' : '✕'} + {ex.name} + {ex.is_running ? 'abandoned · ' : ''}#{ex.task_id} +
+ {/each} +
+ + +``` + +- [ ] **Step 11: Wire OverviewPage (cancelError + Cancelled panel + prop type)** + +In `dashboard/src/components/overview/OverviewPage.svelte`: + +Add the import after line 10 (`import LastExecution ...`): + +```svelte + import CancelledExecutions from './CancelledExecutions.svelte'; + import type { QueuedExecution } from '$lib/api/types'; +``` + +Change the `oncancelexecution` prop type (line 29) from `(_id: number) => void` to `(_ex: QueuedExecution) => void`. + +Replace the Executions Queue card (lines 54-56) with: + +```svelte + + + + {#if executions.cancelled.length} + + + + {/if} +``` + +- [ ] **Step 12: Run tests + typecheck** + +Run: `cd dashboard && npx vitest run tests/executions-store.test.ts tests/executions-queue.test.ts && npm run check` +Expected: all PASS; 0 type errors. + +- [ ] **Step 13: Commit** + +```bash +git add dashboard/src/lib/stores/executions.svelte.ts dashboard/src/lib/stores/modal.svelte.ts dashboard/src/components/modals/CancelExecutionModal.svelte dashboard/src/components/modals/ModalHost.svelte dashboard/src/App.svelte dashboard/src/components/overview/OverviewPage.svelte dashboard/src/components/overview/ExecutionsQueue.svelte dashboard/src/components/overview/CancelledExecutions.svelte dashboard/tests/executions-store.test.ts dashboard/tests/executions-queue.test.ts +git commit -m "feat(dashboard): restore legacy execution cancel + Cancelled Executions panel" +``` + +--- + +### Task 8: `charts.ts` — replace grouped bar with single-series builder + +**Files:** +- Modify: `dashboard/src/lib/charts.ts` +- Modify: `dashboard/tests/charts.test.ts` + +- [ ] **Step 1: Update the failing test first** + +Replace the whole `dashboard/tests/charts.test.ts` with: + +```ts +import { describe, it, expect } from 'vitest'; +import { toPieData, toSingleSeriesBar } from '../src/lib/charts'; +import type { ToolStats } from '../src/lib/api/types'; + +const stats: ToolStats = { + a: { num_times_called: 2, input_tokens: 10, output_tokens: 5 }, + b: { num_times_called: 8, input_tokens: 40, output_tokens: 20 }, +}; + +describe('charts data', () => { + it('builds pie data for a chosen metric, sorted descending', () => { + const pie = toPieData(stats, 'num_times_called'); + expect(pie.labels).toEqual(['b', 'a']); + expect(pie.datasets[0].values).toEqual([8, 2]); + }); + it('builds a single-series bar for one token metric, sorted descending', () => { + const bar = toSingleSeriesBar(stats, 'output_tokens', 'Output Tokens'); + expect(bar.labels).toEqual(['b', 'a']); + expect(bar.datasets).toHaveLength(1); + expect(bar.datasets[0].name).toBe('Output Tokens'); + expect(bar.datasets[0].values).toEqual([20, 5]); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd dashboard && npx vitest run tests/charts.test.ts` +Expected: FAIL — `toSingleSeriesBar` is not exported. + +- [ ] **Step 3: Update `charts.ts`** + +In `dashboard/src/lib/charts.ts`, replace `toGroupedBarData` (lines 20-29) with: + +```ts +export function toSingleSeriesBar( + stats: ToolStats, + key: 'input_tokens' | 'output_tokens', + name: string, +): FrappeData { + const entries = sortedEntries(stats, key); + return { + labels: entries.map(([n]) => n), + datasets: [{ name, values: entries.map(([, s]) => s[key]) }], + }; +} +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `cd dashboard && npx vitest run tests/charts.test.ts` +Expected: PASS (2 tests). + +- [ ] **Step 5: Commit** + +```bash +git add dashboard/src/lib/charts.ts dashboard/tests/charts.test.ts +git commit -m "feat(dashboard): single-series bar builder; drop grouped token bar" +``` + +--- + +### Task 9: `ChartPanel` — value labels + fix `removeChild` teardown error + +**Files:** +- Modify: `dashboard/src/components/stats/ChartPanel.svelte` + +- [ ] **Step 1: Add `valuesOverPoints` prop and guarded teardown** + +Replace the ` +``` + +- [ ] **Step 2: Type-check** + +Run: `cd dashboard && npm run check` +Expected: 0 type errors. (The `valuesOverPoints` option is accepted by the local ambient `frappe-charts` declaration at `src/types/frappe-charts.d.ts`; if `svelte-check` flags an unknown property, add `valuesOverPoints?: number;` to the `ChartArgs`/options interface in that declaration file in this step.) + +- [ ] **Step 3: Commit** + +```bash +git add dashboard/src/components/stats/ChartPanel.svelte +git commit -m "fix(dashboard): guard chart teardown; support on-bar value labels" +``` + +--- + +### Task 10: `StatsPage` — three pies + two single-series bar charts + +**Files:** +- Modify: `dashboard/src/components/stats/StatsPage.svelte` + +- [ ] **Step 1: Update imports and chart markup** + +In `dashboard/src/components/stats/StatsPage.svelte`, change the charts import (line 4): + +```svelte + import { toPieData, toSingleSeriesBar } from '$lib/charts'; +``` + +Replace the `{#if hasStats}` chart block (lines 19-30) with: + +```svelte +{#if hasStats} + +
Token estimator: {stats.estimator}
+
+ + + +
+
+ + +
+{:else} +
No tool stats collected yet.
+{/if} +``` + +Replace the `.charts-container` style rules (lines 38-46) with: + +```css + .charts-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--space-4); + margin-top: var(--space-4); + } + .charts-bars { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-4); + margin-top: var(--space-4); + } + @media (max-width: 1000px) { + .charts-grid { + grid-template-columns: 1fr; + } + } +``` + +- [ ] **Step 2: Type-check** + +Run: `cd dashboard && npm run check` +Expected: 0 type errors. + +- [ ] **Step 3: Commit** + +```bash +git add dashboard/src/components/stats/StatsPage.svelte +git commit -m "feat(dashboard): split token bar into readable per-series charts" +``` + +--- + +### Task 11: `StatsSummary` — add Total Tokens row + +**Files:** +- Modify: `dashboard/src/components/stats/StatsSummary.svelte` +- Test: `dashboard/tests/stats-summary.test.ts` + +- [ ] **Step 1: Write the failing test** + +Create `dashboard/tests/stats-summary.test.ts`: + +```ts +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import StatsSummary from '../src/components/stats/StatsSummary.svelte'; + +describe('StatsSummary', () => { + it('shows a Total tokens row equal to input + output', () => { + render(StatsSummary, { + props: { + stats: { + a: { num_times_called: 1, input_tokens: 10, output_tokens: 5 }, + b: { num_times_called: 2, input_tokens: 40, output_tokens: 20 }, + }, + }, + }); + expect(screen.getByText('Total tokens')).toBeInTheDocument(); + expect(screen.getByTestId('total-tokens')).toHaveTextContent('75'); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd dashboard && npx vitest run tests/stats-summary.test.ts` +Expected: FAIL — no "Total tokens" row / `total-tokens` testid. + +- [ ] **Step 3: Add the row** + +In `dashboard/src/components/stats/StatsSummary.svelte`, replace the `` (lines 17-21) with: + +```svelte + + Total calls{totals.calls} + Total input tokens{totals.input} + Total output tokens{totals.output} + Total tokens{totals.input + totals.output} + +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `cd dashboard && npx vitest run tests/stats-summary.test.ts` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add dashboard/src/components/stats/StatsSummary.svelte dashboard/tests/stats-summary.test.ts +git commit -m "feat(dashboard): restore Total Tokens stats summary row" +``` + +--- + +### Task 12: `ConfigCard` — restore legacy labels/order, remove Client, tooltips, JetBrains mode + +**Files:** +- Modify: `dashboard/src/components/overview/ConfigCard.svelte` +- Test: `dashboard/tests/config-card.test.ts` + +- [ ] **Step 1: Write the failing test** + +Create `dashboard/tests/config-card.test.ts`: + +```ts +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import ConfigCard from '../src/components/overview/ConfigCard.svelte'; +import type { ResponseConfigOverview } from '../src/lib/api/types'; + +function makeConfig(over: Partial = {}): ResponseConfigOverview { + return { + active_project: { name: 'serena', language: 'Python', path: '/x' }, + context: { name: 'claude-code', description: '', path: '/ctx' }, + modes: [{ name: 'editing', path: '/m1' }], + active_tools: ['find_symbol'], + tool_stats_summary: {}, + registered_projects: [], + available_tools: [], + available_modes: [], + available_contexts: [], + available_memories: [], + jetbrains_mode: false, + languages: ['python'], + encoding: 'utf-8', + current_client: 'claude', + serena_version: '1.5.4', + ...over, + }; +} + +const cbs = { + onaddlanguage: vi.fn(), + onremovelanguage: vi.fn(), + oneditconfig: vi.fn(), + onopenmemory: vi.fn(), + oncreatememory: vi.fn(), + ondeletememory: vi.fn(), +}; + +describe('ConfigCard', () => { + it('does not render a Client field', () => { + render(ConfigCard, { props: { data: makeConfig(), ...cbs } }); + expect(screen.queryByText('Client')).toBeNull(); + }); + + it('shows the JetBrains backend notice and hides Add Language in jetbrains mode', () => { + render(ConfigCard, { props: { data: makeConfig({ jetbrains_mode: true }), ...cbs } }); + expect(screen.getByText('Using JetBrains backend')).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Add Language' })).toBeNull(); + }); + + it('shows Add Language when not in jetbrains mode', () => { + render(ConfigCard, { props: { data: makeConfig(), ...cbs } }); + expect(screen.getByRole('button', { name: 'Add Language' })).toBeInTheDocument(); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd dashboard && npx vitest run tests/config-card.test.ts` +Expected: FAIL — "Client" still rendered; no "Using JetBrains backend". + +- [ ] **Step 3: Rewrite the config markup** + +In `dashboard/src/components/overview/ConfigCard.svelte`, replace the `
` block down to the end of the languages `config-row` (lines 24-53) with the restored legacy order (Version → Active Project → Languages inline → Context → Active Modes → File Encoding), tooltips, JetBrains handling, and no Client field: + +```svelte +
+
+ Version + {data.serena_version} + + Active Project + + {#if data.active_project?.name && data.active_project?.path} + {data.active_project.name} + {:else} + {data.active_project?.name ?? 'None'} + {/if} + + + Languages + + {#if data.jetbrains_mode} + Using JetBrains backend + {:else} + + {#each data.languages as lang (lang)} + {lang} + {#if data.languages.length > 1} + + {/if} + + {/each} + {#if data.active_project?.name} + + {/if} + + {/if} + + + Context + {data.context.name} + + Active Modes + + {#if data.modes.length} + {#each data.modes as m, i (m.name)}{m.name}{#if i < data.modes.length - 1}, {/if}{/each} + {:else} + None + {/if} + + + File Encoding + {data.encoding ?? 'N/A'} +
+``` + +(The `Collapsible` Active Tools / Memories blocks and the `config-footer` below this remain unchanged.) + +In the ` +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `cd dashboard && npx vitest run tests/last-execution.test.ts` +Expected: PASS (2 tests). + +- [ ] **Step 5: Commit** + +```bash +git add dashboard/src/components/overview/LastExecution.svelte dashboard/tests/last-execution.test.ts +git commit -m "feat(dashboard): restore boxed Last Execution card with status word and #task_id" +``` + +--- + +### Task 14: Dynamic `document.title` + +**Files:** +- Create: `dashboard/src/lib/title.ts` +- Test: `dashboard/tests/title.test.ts` +- Modify: `dashboard/src/App.svelte` + +- [ ] **Step 1: Write the failing test** + +Create `dashboard/tests/title.test.ts`: + +```ts +import { describe, it, expect } from 'vitest'; +import { pageTitle } from '../src/lib/title'; + +describe('pageTitle', () => { + it('includes the active project when present', () => { + expect(pageTitle('serena')).toBe('serena – Serena Dashboard'); + }); + it('falls back to the bare title when there is no project', () => { + expect(pageTitle(null)).toBe('Serena Dashboard'); + expect(pageTitle(undefined)).toBe('Serena Dashboard'); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd dashboard && npx vitest run tests/title.test.ts` +Expected: FAIL — module not found. + +- [ ] **Step 3: Implement helper** + +Create `dashboard/src/lib/title.ts`: + +```ts +export function pageTitle(project: string | null | undefined): string { + return project ? `${project} – Serena Dashboard` : 'Serena Dashboard'; +} +``` + +- [ ] **Step 4: Use it in `App.svelte`** + +In `dashboard/src/App.svelte`, add the import after line 14 (`import { modal } ...`): + +```svelte + import { pageTitle } from '$lib/title'; +``` + +Add this effect inside ` find_symbol', ['find_symbol']); + expect(html).toContain('<script>'); + expect(html).not.toContain(' + + + + + +``` + +- [ ] **Step 2: Type-check** + +Run: `npm run check` +Expected: 0 errors in ConfirmModal. + +- [ ] **Step 3: Commit** + +```bash +git add dashboard/src/components/modals/ConfirmModal.svelte +git commit -m "feat(dashboard): ConfirmModal wrapper for confirm-style modals" +``` + +### Task 16: Migrate the four confirm modals onto ConfirmModal + +**Files:** +- Modify: `dashboard/src/components/modals/RemoveLanguageModal.svelte` +- Modify: `dashboard/src/components/modals/DeleteMemoryModal.svelte` +- Modify: `dashboard/src/components/modals/CancelExecutionModal.svelte` +- Modify: `dashboard/src/components/modals/ShutdownModal.svelte` + +- [ ] **Step 1: RemoveLanguageModal** + +Overwrite `dashboard/src/components/modals/RemoveLanguageModal.svelte`: + +```svelte + + + runMutation(() => removeLanguage(language))} {onclose}> + Remove language {language} from configuration? + +``` + +- [ ] **Step 2: DeleteMemoryModal** + +Overwrite `dashboard/src/components/modals/DeleteMemoryModal.svelte`: + +```svelte + + + runMutation(() => deleteMemory(name))} {onclose}> + Delete memory {name}? + +``` + +- [ ] **Step 3: CancelExecutionModal** (uses the store's `cancel`, which already returns a MutationResult; clears the store error on close) + +Overwrite `dashboard/src/components/modals/CancelExecutionModal.svelte`: + +```svelte + + + executions.cancel(execution)} onclose={handleClose}> + Are you sure? The execution will continue running until timeout, it will simply no longer be in + the queue. Abandoning a running execution is only advised as a measure for unblocking Serena. + +``` + +- [ ] **Step 4: ShutdownModal** (fixes finding 2 — only close on success) + +Overwrite `dashboard/src/components/modals/ShutdownModal.svelte`: + +```svelte + + + + Shut down the Serena server? + +``` + +- [ ] **Step 5: Type-check and run the relevant tests** + +Run: `npm run check && npm test -- delete-memory-modal executions` +Expected: 0 type errors; `delete-memory-modal` still passes (button label `OK`, inline error). The merged `executions-queue` test is unaffected (it renders `ExecutionsQueue`, not the modal). + +- [ ] **Step 6: Commit** + +```bash +git add dashboard/src/components/modals/RemoveLanguageModal.svelte dashboard/src/components/modals/DeleteMemoryModal.svelte dashboard/src/components/modals/CancelExecutionModal.svelte dashboard/src/components/modals/ShutdownModal.svelte +git commit -m "refactor(dashboard): confirm modals use ConfirmModal; fix shutdown error handling" +``` + +### Task 17: Refactor input/editor modals onto createModalAction + shared CSS + +**Files:** +- Modify: `dashboard/src/components/modals/AddLanguageModal.svelte` +- Modify: `dashboard/src/components/modals/CreateMemoryModal.svelte` +- Modify: `dashboard/src/components/modals/EditSerenaConfigModal.svelte` + +- [ ] **Step 1: AddLanguageModal** — use `createModalAction`, drop local busy/error and the inline `.modal-info`/`.modal-hint`/`.modal-actions` styles (now global). Add an `aria-label` to the combobox via existing markup (the Combobox input gets a label in Task 20; here only the action refactor): + +Overwrite `dashboard/src/components/modals/AddLanguageModal.svelte`: + +```svelte + + + + + + (selected = v)} /> + + +``` + +(No ` +``` + +(The local `.modal-textarea` and `.modal-actions` blocks are removed — they are global now. `.rename-confirm` had no rules previously; keep it unstyled.) + +- [ ] **Step 3: Type-check, run the edit-memory test and confirm warnings are gone** + +Run: `npm run check` +Expected: 0 errors AND the two `state_referenced_locally` warnings for `EditMemoryModal.svelte:11,18` are gone (the eslint-disable comments and the suppressed pattern were removed by the rewrite). + +Run: `npm test -- edit-memory-modal` +Expected: PASS (dirty-guard behavior preserved). + +- [ ] **Step 4: Commit** + +```bash +git add dashboard/src/components/modals/EditMemoryModal.svelte dashboard/src/components/modals/ModalHost.svelte +git commit -m "refactor(dashboard): EditMemoryModal createModalAction, rename busy/Enter, key remount" +``` + +--- + +## Phase 5 — Card dedup + accessibility + +### Task 19: Compose `` in ListPanel and ProjectsPanel + +`Card.svelte` renders `
{children}
` with the exact `.card` style these panels duplicate. Compose it instead. + +**Files:** +- Modify: `dashboard/src/components/overview/ListPanel.svelte` +- Modify: `dashboard/src/components/overview/ProjectsPanel.svelte` + +- [ ] **Step 1: ListPanel** — wrap in ``, drop the duplicated `.card` style block: + +Overwrite `dashboard/src/components/overview/ListPanel.svelte`: + +```svelte + + + + + {#if items.length} +
    + {#each items as item (item.name)}
  • {item.name}
  • {/each} +
+ {:else} +
None.
+ {/if} +
+
+ + +``` + +- [ ] **Step 2: ProjectsPanel** — same; drop the `.card` block, keep everything else: + +In `dashboard/src/components/overview/ProjectsPanel.svelte`: +- Add `import Card from '../common/Card.svelte';` after the `ProjectInfo` import. +- Replace the wrapping `
` (lines 7-22) with `` (same inner content). +- Delete the `.card { … }` rule (lines 25-32) from the ` +``` + +Note on effect ordering: the create-effect reads only `theme.current` and `el`, so a data change does NOT re-run it; the second effect reads `data` and calls `update()`. On first mount both run — create builds the chart and sets `chartRef`; the update-effect then calls `update(data)` once with the same data (idempotent, cheap). On theme change the create-effect tears down and rebuilds with fresh colors. + +- [ ] **Step 2: Type-check** + +Run: `npm run check` +Expected: 0 errors. + +- [ ] **Step 3: Commit** + +```bash +git add dashboard/src/components/stats/ChartPanel.svelte +git commit -m "perf(dashboard): ChartPanel update() in place; remove removeChild suppressor" +``` + +### Task 24: Full verification, build, and bundle commit + +**Files:** +- Modify (generated): `dashboard/../src/serena/resources/dashboard/assets/*` + +- [ ] **Step 1: Format, lint, type-check, test** + +Run: `npm run format && npm run lint && npm run check && npm test` +Expected: prettier writes nothing new (or stage what it changes), eslint clean, 0 type errors, 0 `state_referenced_locally` warnings remaining (Collapsible/Combobox still have theirs — see Step 2), all tests pass. + +- [ ] **Step 2: Confirm warning state** + +Run: `npm run check 2>&1 | grep -i "state_referenced_locally" || echo "none"` +Expected: Only `Collapsible.svelte` and `Combobox.svelte` may remain (their `open`/`value` initial-capture is out of scope for this plan and documented as initial-only). `EditMemoryModal` warnings must be GONE. If `EditMemoryModal` still appears, the Task 18 rewrite is incomplete. + +- [ ] **Step 3: Build the bundle** + +Run: `npm run build` +Expected: succeeds; regenerates `../src/serena/resources/dashboard/assets/index-*.js` and `index-*.css` (the `prebuild` clean removes stale hashed files). + +- [ ] **Step 4: Manual runtime verification (REQUIRED — the suppressor was removed)** + +Start a Serena MCP server with the dashboard enabled (default port 24282), open the dashboard, and in the browser devtools console confirm: +- Stats page: click Refresh Stats — all 5 charts render; click Refresh again (data update path) — charts update with **no `removeChild`/`NotFoundError` console errors**. +- Toggle theme on the Stats page — charts recreate and recolor with no console errors. +- Light AND dark: Overview cards (Registered Projects / list panels) match the previous look (Card composition). +- Open each modal (Add Language, Create/Edit/Delete Memory, Edit Config, Cancel Execution, Shutdown — do NOT confirm Shutdown): inline errors and busy spinners work; Tab stays within the dialog; Escape closes. +- Logs page: tool names still highlight correctly. + +If any `removeChild` console error appears on the chart paths, reinstate the suppressor: restore the ` + + +``` + +- [ ] **Step 6: Create `dashboard/src/main.ts`** + +```ts +import { mount } from 'svelte'; +import './styles/tokens.css'; +import './styles/global.css'; +import App from './App.svelte'; + +const app = mount(App, { target: document.getElementById('app')! }); +export default app; +``` + +- [ ] **Step 7: Create placeholder `dashboard/src/App.svelte`** (replaced in Phase 2) + +```svelte + + +
Serena Dashboard v2 — scaffold OK
+``` + +- [ ] **Step 8: Create placeholder style files so `main.ts` imports resolve** + +Create `dashboard/src/styles/tokens.css` with `:root {}` and `dashboard/src/styles/global.css` empty. (Filled in Phase 1.) + +- [ ] **Step 9: Create `dashboard/.gitignore`** + +``` +node_modules +dist +.vite +*.local +``` + +- [ ] **Step 10: Install and verify dev server boots** + +Run: `cd dashboard && npm install && npm run check` +Expected: install succeeds; `svelte-check` reports 0 errors. + +- [ ] **Step 11: Commit** + +```bash +git add dashboard/ && git commit -m "chore(dashboard): scaffold Svelte 5 + Vite + TS project" +``` + +### Task 0.2: Configure testing, lint, and the subproject CLAUDE.md + +**Files:** +- Create: `dashboard/tests/setup.ts` +- Create: `dashboard/eslint.config.js` +- Create: `dashboard/.prettierrc` +- Create: `dashboard/CLAUDE.md` + +- [ ] **Step 1: Create `dashboard/tests/setup.ts`** + +```ts +import '@testing-library/jest-dom/vitest'; +``` + +- [ ] **Step 2: Create `dashboard/eslint.config.js`** + +```js +import js from '@eslint/js'; +import svelte from 'eslint-plugin-svelte'; +export default [ + js.configs.recommended, + ...svelte.configs['flat/recommended'], + { ignores: ['node_modules/', 'dist/'] }, +]; +``` + +- [ ] **Step 3: Create `dashboard/.prettierrc`** + +```json +{ "singleQuote": true, "printWidth": 100, "plugins": ["prettier-plugin-svelte"] } +``` + +- [ ] **Step 4: Add a trivial smoke test `dashboard/tests/smoke.test.ts`** + +```ts +import { describe, it, expect } from 'vitest'; +describe('test harness', () => { + it('runs', () => { expect(1 + 1).toBe(2); }); +}); +``` + +- [ ] **Step 5: Run the test suite** + +Run: `cd dashboard && npm test` +Expected: 1 passing test. + +- [ ] **Step 6: Create `dashboard/CLAUDE.md`** (full content below) + +```markdown +# Serena Dashboard (frontend) + +This directory is the **frontend only**. The dashboard's HTTP API lives in +`../src/serena/dashboard.py` and is a **frozen contract** — never change endpoint +names, request/response shapes, ports, or the host-header check from here. + +## Commands (run from `dashboard/`) +- `npm install` — install deps +- `npm run dev` — Vite dev server (proxies API to a running Serena dashboard on :24282) +- `npm run build` — build hashed assets into `../src/serena/resources/dashboard/` +- `npm run check` — type-check (`svelte-check`) +- `npm test` — Vitest +- `npm run lint` / `npm run format` — ESLint + Prettier + +## Running the backend for `npm run dev` +Start any Serena MCP server with the dashboard enabled, note its port (default 24282), +and set it as the proxy target in `vite.config.ts` if it differs. + +## Architecture rules +- Components are small and single-purpose: props in, events out, scoped CSS. +- State lives in runes stores under `src/lib/stores/` (`.svelte.ts`). +- All network access goes through `src/lib/api/`. Never `fetch` from a component. +- Never reintroduce jQuery. +- Colors come from `src/styles/tokens.css`. Never hardcode hex in a component. +- Charts go through `src/components/stats/ChartPanel.svelte` (Frappe Charts wrapper). + +## The contract rule (CI enforces this) +After any change, run `npm run build` and commit the regenerated +`../src/serena/resources/dashboard/` output. CI rebuilds and fails the PR if the +committed output is stale. + +## Adding a feature that needs a backend route +1. Add the response/request type to `src/lib/api/types.ts`. +2. Add a typed function to `src/lib/api/endpoints.ts`. +3. Add/extend a store in `src/lib/stores/`. +4. Build the component and a Vitest test. + +## Visual parity +The app must match the legacy dashboard in both light and dark themes. Compare +side-by-side before merging. The palette is defined once in `tokens.css`. +``` + +- [ ] **Step 7: Commit** + +```bash +git add dashboard/ && git commit -m "chore(dashboard): add test setup, lint config, and CLAUDE.md" +``` + +--- + +## Phase 1 — Foundation: design tokens, API layer, stores, utilities + +### Task 1.1: Port the design tokens (palette) verbatim + +**Files:** +- Modify: `dashboard/src/styles/tokens.css` +- Create: `dashboard/src/styles/global.css` (base element styles) + +Source of truth is the legacy `src/serena/resources/dashboard/dashboard.css` `:root` and dark-theme blocks. Reproduce values exactly. + +- [ ] **Step 1: Write `dashboard/src/styles/tokens.css`** + +```css +:root { + --bg: #f5f5f5; + --bg-card: #ffffff; + --bg-elevated: #ffffff; + --bg-secondary-btn: #f0f2f5; + --text-primary: #1f2328; + --text-secondary: #3f4754; + --text-muted: #6a737d; + --border: #e3e6ea; + --border-strong: #d0d7de; + --accent: #eaa45d; + --accent-hover: #dca662; + --btn-disabled: #adb5bd; + --tool-highlight: #fff3bf; + --success: #22c55e; + --log-debug: #8b95a1; + --log-info: #1f2328; + --log-warning: #d97706; + --log-error: #dc2626; + --radius: 6px; + --radius-sm: 4px; + --space-1: 4px; --space-2: 8px; --space-3: 12px; + --space-4: 16px; --space-6: 24px; --space-8: 32px; + --max-width: 1600px; + --font-sans: 'Inter', system-ui, -apple-system, sans-serif; + --font-mono: 'JetBrains Mono', monospace; +} + +[data-theme='dark'] { + --bg: #1a1a1a; + --bg-card: #2d2d2d; + --bg-elevated: #262b32; + --bg-secondary-btn: #262b32; + --text-primary: #e6edf3; + --text-secondary: #c9d1d9; + --text-muted: #8b95a1; + --border: #2d333b; + --border-strong: #3d444d; + --tool-highlight: #f6c948; + --log-info: #e6edf3; + --log-warning: #f59e0b; + --log-error: #f87171; +} +``` + +- [ ] **Step 2: Write `dashboard/src/styles/global.css`** (body, headings, scrollbars, link reset — port the equivalent base rules from legacy `dashboard.css`) + +```css +* { box-sizing: border-box; } +body { + margin: 0; + font-family: var(--font-sans); + background: var(--bg); + color: var(--text-primary); +} +h1, h2, h3 { font-weight: 600; } +code, pre, .mono { font-family: var(--font-mono); } +a { color: inherit; } +``` + +- [ ] **Step 3: Verify build still compiles** + +Run: `cd dashboard && npm run check` +Expected: 0 errors. + +- [ ] **Step 4: Commit** + +```bash +git add dashboard/src/styles && git commit -m "feat(dashboard): port design tokens and base styles" +``` + +### Task 1.2: Define API types mirroring the Pydantic models + +**Files:** +- Create: `dashboard/src/lib/api/types.ts` + +These types mirror `src/serena/dashboard.py` and `ToolUsageStats.Entry`. Keep field names identical to the JSON. + +- [ ] **Step 1: Write `dashboard/src/lib/api/types.ts`** + +```ts +// Mirrors src/serena/dashboard.py response/request models. Field names must match JSON. + +export interface ToolStatEntry { + num_times_called: number; + input_tokens: number; + output_tokens: number; +} +export type ToolStats = Record; + +// NOTE: /get_config_overview's tool_stats_summary is a DIFFERENT, reduced shape — +// the backend renames num_times_called -> num_calls and drops the token fields +// (dashboard.py:564). It is NOT a ToolStats. +export interface ToolSummaryEntry { num_calls: number; } +export type ToolStatsSummary = Record; + +export interface ResponseLog { + messages: string[]; + max_idx: number; + active_project: string | null; +} + +export interface ResponseToolNames { tool_names: string[]; } +export interface ResponseToolStats { stats: ToolStats; } + +// Precise nested shapes verified against ResponseConfigOverview in dashboard.py. +export interface ActiveProject { + name: string | null; + language: string | null; // comma-separated, e.g. "Python, TypeScript" + path: string | null; +} +export interface ContextInfo { name: string; description: string; path: string; } +export interface ModeInfo { name: string; description?: string; path: string; is_active?: boolean; } +export interface ProjectInfo { name: string; path: string; is_active: boolean; } +export interface ToolInfo { name: string; is_active: boolean; } +export interface ContextOption { name: string; is_active: boolean; path: string; } + +export interface ResponseConfigOverview { + active_project: ActiveProject | null; + context: ContextInfo; + modes: ModeInfo[]; + active_tools: string[]; + tool_stats_summary: ToolStatsSummary; + registered_projects: ProjectInfo[]; + available_tools: ToolInfo[]; + available_modes: ModeInfo[]; + available_contexts: ContextOption[]; + available_memories: string[] | null; + jetbrains_mode: boolean; + languages: string[]; + encoding: string | null; + current_client: string | null; + serena_version: string; +} + +export interface ResponseAvailableLanguages { languages: string[]; } +export interface ResponseGetMemory { content: string; memory_name: string; } +export interface ResponseGetSerenaConfig { content: string; } + +export interface QueuedExecution { + task_id: number; + is_running: boolean; + name: string; + finished_successfully: boolean; + logged: boolean; +} +export interface ResponseQueuedExecutions { queued_executions: QueuedExecution[]; status: string; } +export interface ResponseLastExecution { last_execution: QueuedExecution | null; status: string; } +export interface ResponseCancelExecution { status: string; was_cancelled: boolean; message?: string; } + +// News ids are YYYYMMDD strings; values are HTML snippets. +export interface ResponseNews { news: Record; status: string; } + +// Generic mutation responses ({status, message}) for add/remove/save/delete/rename. +export interface StatusResponse { status: 'success' | 'error'; message?: string; } +export interface TokenEstimatorResponse { token_count_estimator_name: string; } +``` + +- [ ] **Step 2: Type-check** + +Run: `cd dashboard && npm run check` +Expected: 0 errors. + +- [ ] **Step 3: Commit** + +```bash +git add dashboard/src/lib/api/types.ts && git commit -m "feat(dashboard): add typed API models" +``` + +### Task 1.3: Build the typed fetch client (TDD) + +**Files:** +- Create: `dashboard/src/lib/api/client.ts` +- Test: `dashboard/tests/client.test.ts` + +- [ ] **Step 1: Write the failing test `dashboard/tests/client.test.ts`** + +```ts +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { getJson, postJson, ApiError } from '../src/lib/api/client'; + +beforeEach(() => { vi.restoreAllMocks(); }); + +describe('client', () => { + it('getJson parses JSON on success', async () => { + vi.stubGlobal('fetch', vi.fn().mockResolvedValue( + new Response(JSON.stringify({ a: 1 }), { status: 200 }), + )); + expect(await getJson<{ a: number }>('/x')).toEqual({ a: 1 }); + }); + + it('postJson sends a JSON body', async () => { + const f = vi.fn().mockResolvedValue(new Response('{}', { status: 200 })); + vi.stubGlobal('fetch', f); + await postJson('/y', { name: 'go' }); + expect(f).toHaveBeenCalledWith('/y', expect.objectContaining({ + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name: 'go' }), + })); + }); + + it('throws ApiError on non-2xx', async () => { + vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('nope', { status: 500 }))); + await expect(getJson('/z')).rejects.toBeInstanceOf(ApiError); + }); +}); +``` + +- [ ] **Step 2: Run it — expect failure** + +Run: `cd dashboard && npm test -- client` +Expected: FAIL (module not found). + +- [ ] **Step 3: Implement `dashboard/src/lib/api/client.ts`** + +```ts +export class ApiError extends Error { + constructor(public status: number, message: string) { super(message); } +} + +async function handle(res: Response): Promise { + if (!res.ok) throw new ApiError(res.status, `HTTP ${res.status} for ${res.url}`); + return (await res.json()) as T; +} + +export async function getJson(path: string): Promise { + return handle(await fetch(path)); +} + +export async function postJson(path: string, body?: unknown): Promise { + return handle(await fetch(path, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body ?? {}), + })); +} + +export async function putJson(path: string): Promise { + return handle(await fetch(path, { method: 'PUT' })); +} +``` + +- [ ] **Step 4: Run the test — expect pass** + +Run: `cd dashboard && npm test -- client` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add dashboard/src/lib/api/client.ts dashboard/tests/client.test.ts +git commit -m "feat(dashboard): typed fetch client with ApiError" +``` + +### Task 1.4: Define the endpoints module + +**Files:** +- Create: `dashboard/src/lib/api/endpoints.ts` +- Test: `dashboard/tests/endpoints.test.ts` + +- [ ] **Step 1: Write the failing test `dashboard/tests/endpoints.test.ts`** (verifies each function hits the right URL/method) + +```ts +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import * as api from '../src/lib/api/endpoints'; + +let fetchMock: ReturnType; +beforeEach(() => { + fetchMock = vi.fn().mockResolvedValue(new Response('{}', { status: 200 })); + vi.stubGlobal('fetch', fetchMock); +}); + +describe('endpoints', () => { + it('fetchConfigOverview GETs /get_config_overview', async () => { + await api.fetchConfigOverview(); + expect(fetchMock).toHaveBeenCalledWith('/get_config_overview'); + }); + it('fetchLogMessages POSTs /get_log_messages with start_idx', async () => { + await api.fetchLogMessages(5); + expect(fetchMock).toHaveBeenCalledWith('/get_log_messages', expect.objectContaining({ + method: 'POST', body: JSON.stringify({ start_idx: 5 }), + })); + }); + it('shutdown PUTs /shutdown', async () => { + await api.shutdown(); + expect(fetchMock).toHaveBeenCalledWith('/shutdown', expect.objectContaining({ method: 'PUT' })); + }); +}); +``` + +- [ ] **Step 2: Run it — expect failure.** Run: `cd dashboard && npm test -- endpoints` → FAIL. + +- [ ] **Step 3: Implement `dashboard/src/lib/api/endpoints.ts`** + +```ts +import { getJson, postJson, putJson } from './client'; +import type { + ResponseLog, ResponseToolNames, ResponseToolStats, ResponseConfigOverview, + ResponseAvailableLanguages, ResponseGetMemory, ResponseGetSerenaConfig, + ResponseQueuedExecutions, ResponseLastExecution, ResponseCancelExecution, + ResponseNews, StatusResponse, TokenEstimatorResponse, +} from './types'; + +export const fetchConfigOverview = () => getJson('/get_config_overview'); +export const fetchLogMessages = (startIdx: number) => postJson('/get_log_messages', { start_idx: startIdx }); +export const clearLogs = () => postJson('/clear_logs'); +export const fetchToolNames = () => getJson('/get_tool_names'); +export const fetchToolStats = () => getJson('/get_tool_stats'); +export const clearToolStats = () => postJson('/clear_tool_stats'); +export const fetchEstimatorName = () => getJson('/get_token_count_estimator_name'); +export const shutdown = () => putJson('/shutdown'); +export const fetchAvailableLanguages = () => getJson('/get_available_languages'); +export const addLanguage = (language: string) => postJson('/add_language', { language }); +export const removeLanguage = (language: string) => postJson('/remove_language', { language }); +export const getMemory = (memory_name: string) => postJson('/get_memory', { memory_name }); +export const saveMemory = (memory_name: string, content: string) => postJson('/save_memory', { memory_name, content }); +export const deleteMemory = (memory_name: string) => postJson('/delete_memory', { memory_name }); +export const renameMemory = (old_name: string, new_name: string) => postJson('/rename_memory', { old_name, new_name }); +export const getSerenaConfig = () => getJson('/get_serena_config'); +export const saveSerenaConfig = (content: string) => postJson('/save_serena_config', { content }); +export const fetchQueuedExecutions = () => getJson('/queued_task_executions'); +export const cancelExecution = (task_id: number) => postJson('/cancel_task_execution', { task_id }); +export const fetchLastExecution = () => getJson('/last_execution'); +export const fetchUnreadNews = () => getJson('/fetch_unread_news'); +export const markNewsRead = (news_snippet_id: string) => postJson('/mark_news_snippet_as_read', { news_snippet_id }); +``` + +- [ ] **Step 4: Run the test — expect pass.** Run: `cd dashboard && npm test -- endpoints` → PASS. + +- [ ] **Step 5: Commit** + +```bash +git add dashboard/src/lib/api/endpoints.ts dashboard/tests/endpoints.test.ts +git commit -m "feat(dashboard): typed endpoints module for all backend routes" +``` + +### Task 1.5: Polling utility (TDD) + +**Files:** +- Create: `dashboard/src/lib/polling.ts` +- Test: `dashboard/tests/polling.test.ts` + +Replaces scattered `setInterval`s. Calls `fn` immediately, then on an interval; `stop()` cancels; overlapping runs are prevented (skip a tick if the previous call is still in flight). + +- [ ] **Step 1: Write the failing test `dashboard/tests/polling.test.ts`** + +```ts +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { createPoller } from '../src/lib/polling'; + +beforeEach(() => vi.useFakeTimers()); +afterEach(() => vi.useRealTimers()); + +describe('createPoller', () => { + it('runs immediately and on each interval, and stops', async () => { + const fn = vi.fn().mockResolvedValue(undefined); + const poller = createPoller(fn, 1000); + poller.start(); + expect(fn).toHaveBeenCalledTimes(1); + await vi.advanceTimersByTimeAsync(1000); + expect(fn).toHaveBeenCalledTimes(2); + poller.stop(); + await vi.advanceTimersByTimeAsync(2000); + expect(fn).toHaveBeenCalledTimes(2); + }); +}); +``` + +- [ ] **Step 2: Run it — expect failure.** Run: `cd dashboard && npm test -- polling` → FAIL. + +- [ ] **Step 3: Implement `dashboard/src/lib/polling.ts`** + +```ts +export interface Poller { start(): void; stop(): void; } + +export function createPoller(fn: () => Promise | void, intervalMs: number): Poller { + let timer: ReturnType | null = null; + let inFlight = false; + + const tick = async () => { + if (inFlight) return; + inFlight = true; + try { await fn(); } finally { inFlight = false; } + }; + + return { + start() { + if (timer) return; + void tick(); + timer = setInterval(() => void tick(), intervalMs); + }, + stop() { + if (timer) { clearInterval(timer); timer = null; } + }, + }; +} +``` + +- [ ] **Step 4: Run the test — expect pass.** Run: `cd dashboard && npm test -- polling` → PASS. + +- [ ] **Step 5: Commit** + +```bash +git add dashboard/src/lib/polling.ts dashboard/tests/polling.test.ts +git commit -m "feat(dashboard): reusable poller utility" +``` + +### Task 1.6: Log formatting utility (TDD) + +**Files:** +- Create: `dashboard/src/lib/format.ts` +- Test: `dashboard/tests/format.test.ts` + +Ports the legacy `LogMessage` behavior: HTML-escape text, detect level (DEBUG/INFO/WARNING/ERROR), and wrap known tool names in ``. + +- [ ] **Step 1: Write the failing test `dashboard/tests/format.test.ts`** + +```ts +import { describe, it, expect } from 'vitest'; +import { detectLevel, escapeHtml, highlightTools } from '../src/lib/format'; + +describe('format', () => { + it('detects log level', () => { + expect(detectLevel('2026-01-01 INFO something')).toBe('info'); + expect(detectLevel('WARNING watch out')).toBe('warning'); + expect(detectLevel('no level here')).toBe('info'); + }); + it('escapes html', () => { + expect(escapeHtml('&"')).toBe('<b>&"'); + }); + it('highlights known tool names', () => { + const html = highlightTools('called find_symbol now', ['find_symbol']); + expect(html).toContain('find_symbol'); + }); +}); +``` + +- [ ] **Step 2: Run it — expect failure.** Run: `cd dashboard && npm test -- format` → FAIL. + +- [ ] **Step 3: Implement `dashboard/src/lib/format.ts`** + +```ts +export type LogLevel = 'debug' | 'info' | 'warning' | 'error'; + +export function detectLevel(line: string): LogLevel { + if (/\bERROR\b/.test(line)) return 'error'; + if (/\bWARNING\b/.test(line)) return 'warning'; + if (/\bDEBUG\b/.test(line)) return 'debug'; + return 'info'; +} + +export function escapeHtml(s: string): string { + return s + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} + +export function highlightTools(text: string, toolNames: string[]): string { + let html = escapeHtml(text); + for (const name of toolNames) { + const re = new RegExp(`\\b${name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\b`, 'g'); + html = html.replace(re, `${name}`); + } + return html; +} +``` + +- [ ] **Step 4: Run the test — expect pass.** Run: `cd dashboard && npm test -- format` → PASS. + +- [ ] **Step 5: Commit** + +```bash +git add dashboard/src/lib/format.ts dashboard/tests/format.test.ts +git commit -m "feat(dashboard): log formatting and tool-name highlighting" +``` + +### Task 1.7: Theme store (TDD) + +**Files:** +- Create: `dashboard/src/lib/stores/theme.svelte.ts` +- Test: `dashboard/tests/theme.test.ts` + +Reads localStorage (`serena-dashboard-theme`), falls back to `prefers-color-scheme`, writes `data-theme` on ``, persists on toggle. + +- [ ] **Step 1: Write the failing test `dashboard/tests/theme.test.ts`** + +```ts +import { describe, it, expect, beforeEach } from 'vitest'; +import { createThemeStore } from '../src/lib/stores/theme.svelte'; + +beforeEach(() => { + localStorage.clear(); + document.documentElement.removeAttribute('data-theme'); +}); + +describe('theme store', () => { + it('persists and applies the theme on toggle', () => { + const t = createThemeStore(); + t.init(); + const first = t.current; + t.toggle(); + expect(t.current).not.toBe(first); + expect(document.documentElement.getAttribute('data-theme')).toBe(t.current); + expect(localStorage.getItem('serena-dashboard-theme')).toBe(t.current); + }); +}); +``` + +- [ ] **Step 2: Run it — expect failure.** Run: `cd dashboard && npm test -- theme` → FAIL. + +- [ ] **Step 3: Implement `dashboard/src/lib/stores/theme.svelte.ts`** + +```ts +export type Theme = 'light' | 'dark'; +const KEY = 'serena-dashboard-theme'; + +export function createThemeStore() { + let current = $state('light'); + + function apply(t: Theme) { + current = t; + document.documentElement.setAttribute('data-theme', t); + } + + return { + get current() { return current; }, + init() { + const stored = localStorage.getItem(KEY) as Theme | null; + const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)').matches; + apply(stored ?? (prefersDark ? 'dark' : 'light')); + }, + toggle() { + const next: Theme = current === 'dark' ? 'light' : 'dark'; + apply(next); + localStorage.setItem(KEY, next); + }, + }; +} + +export const theme = createThemeStore(); +``` + +- [ ] **Step 4: Run the test — expect pass.** Run: `cd dashboard && npm test -- theme` → PASS. + +- [ ] **Step 5: Commit** + +```bash +git add dashboard/src/lib/stores/theme.svelte.ts dashboard/tests/theme.test.ts +git commit -m "feat(dashboard): theme store with persistence and system fallback" +``` + +### Task 1.8: Config, logs, executions, stats stores + +**Files:** +- Create: `dashboard/src/lib/stores/config.svelte.ts` +- Create: `dashboard/src/lib/stores/logs.svelte.ts` +- Create: `dashboard/src/lib/stores/executions.svelte.ts` +- Create: `dashboard/src/lib/stores/stats.svelte.ts` +- Test: `dashboard/tests/logs-store.test.ts` + +The logs store holds the incremental-append + "skip if unchanged" logic, which is the most error-prone, so it gets a test. + +- [ ] **Step 1: Write the failing test `dashboard/tests/logs-store.test.ts`** + +```ts +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { createLogsStore } from '../src/lib/stores/logs.svelte'; + +beforeEach(() => vi.restoreAllMocks()); + +describe('logs store', () => { + it('appends only new messages by tracking max_idx', async () => { + const responses = [ + { messages: ['a', 'b'], max_idx: 2, active_project: 'p' }, + { messages: ['c'], max_idx: 3, active_project: 'p' }, + ]; + let call = 0; + vi.stubGlobal('fetch', vi.fn().mockImplementation(() => + Promise.resolve(new Response(JSON.stringify(responses[call++]), { status: 200 })))); + const store = createLogsStore(); + await store.poll(); + expect(store.lines).toEqual(['a', 'b']); + await store.poll(); + expect(store.lines).toEqual(['a', 'b', 'c']); + }); +}); +``` + +- [ ] **Step 2: Run it — expect failure.** Run: `cd dashboard && npm test -- logs-store` → FAIL. + +- [ ] **Step 3: Implement `dashboard/src/lib/stores/logs.svelte.ts`** + +```ts +import { fetchLogMessages, clearLogs as apiClearLogs } from '$lib/api/endpoints'; + +export function createLogsStore() { + let lines = $state([]); + let nextIdx = $state(0); + let activeProject = $state(null); + + return { + get lines() { return lines; }, + get activeProject() { return activeProject; }, + async poll() { + const res = await fetchLogMessages(nextIdx); + if (res.messages.length) lines = [...lines, ...res.messages]; + nextIdx = res.max_idx; + activeProject = res.active_project; + }, + async clear() { + await apiClearLogs(); + lines = []; + nextIdx = 0; + }, + }; +} + +export const logs = createLogsStore(); +``` + +- [ ] **Step 4: Run the test — expect pass.** Run: `cd dashboard && npm test -- logs-store` → PASS. + +- [ ] **Step 5: Implement `dashboard/src/lib/stores/config.svelte.ts`** (skip re-render via JSON hash compare) + +```ts +import { fetchConfigOverview } from '$lib/api/endpoints'; +import type { ResponseConfigOverview } from '$lib/api/types'; + +export function createConfigStore() { + let data = $state(null); + let lastJson = ''; + + return { + get data() { return data; }, + async poll() { + const next = await fetchConfigOverview(); + const json = JSON.stringify(next); + if (json !== lastJson) { data = next; lastJson = json; } + }, + }; +} + +export const config = createConfigStore(); +``` + +- [ ] **Step 6: Implement `dashboard/src/lib/stores/executions.svelte.ts`** + +```ts +import { fetchQueuedExecutions, fetchLastExecution, cancelExecution } from '$lib/api/endpoints'; +import type { QueuedExecution } from '$lib/api/types'; + +export function createExecutionsStore() { + let queued = $state([]); + let last = $state(null); + + return { + get queued() { return queued; }, + get last() { return last; }, + async pollQueued() { queued = (await fetchQueuedExecutions()).queued_executions; }, + async pollLast() { last = (await fetchLastExecution()).last_execution; }, + async cancel(taskId: number) { return cancelExecution(taskId); }, + }; +} + +export const executions = createExecutionsStore(); +``` + +- [ ] **Step 7: Implement `dashboard/src/lib/stores/stats.svelte.ts`** + +```ts +import { fetchToolStats, clearToolStats, fetchEstimatorName } from '$lib/api/endpoints'; +import type { ToolStats } from '$lib/api/types'; + +export function createStatsStore() { + let stats = $state({}); + let estimator = $state('unknown'); + + return { + get stats() { return stats; }, + get estimator() { return estimator; }, + async refresh() { + stats = (await fetchToolStats()).stats; + estimator = (await fetchEstimatorName()).token_count_estimator_name; + }, + async clear() { await clearToolStats(); stats = {}; }, + }; +} + +export const stats = createStatsStore(); +``` + +- [ ] **Step 8: Type-check + test.** Run: `cd dashboard && npm run check && npm test` → 0 errors, all pass. + +- [ ] **Step 9: Commit** + +```bash +git add dashboard/src/lib/stores dashboard/tests/logs-store.test.ts +git commit -m "feat(dashboard): config, logs, executions, and stats stores" +``` + +--- + +## Phase 2 — App shell and common components + +### Task 2.1: Common UI primitives + +**Files:** +- Create: `dashboard/src/components/common/Spinner.svelte` +- Create: `dashboard/src/components/common/Modal.svelte` +- Create: `dashboard/src/components/common/Collapsible.svelte` +- Create: `dashboard/src/components/common/Combobox.svelte` +- Test: `dashboard/tests/combobox.test.ts` + +- [ ] **Step 1: Implement `Spinner.svelte`** (port the 16px rotating spinner; orange top border) + +```svelte +
+ +``` + +- [ ] **Step 2: Implement `Modal.svelte`** (backdrop with blur, close on ✕/backdrop/Escape, slot for content). Uses Svelte 5 snippets. + +```svelte + + + +{#if open} + +{/if} + + +``` + +- [ ] **Step 3: Implement `Collapsible.svelte`** (header with rotating ▼ chevron; content toggles). + +```svelte + + +
+

(expanded = !expanded)}> + {title} + +

+ {#if expanded}
{@render children()}
{/if} +
+ + +``` + +- [ ] **Step 4: Write the failing test `dashboard/tests/combobox.test.ts`** for the combobox filtering behavior + +```ts +import { describe, it, expect } from 'vitest'; +import { render, fireEvent } from '@testing-library/svelte'; +import Combobox from '../src/components/common/Combobox.svelte'; + +describe('Combobox', () => { + it('filters options by typed text', async () => { + const { getByRole, queryByText, getByText } = render(Combobox, { + props: { options: ['python', 'typescript', 'rust'], value: '', onselect: () => {} }, + }); + await fireEvent.input(getByRole('textbox'), { target: { value: 'ty' } }); + expect(getByText('typescript')).toBeInTheDocument(); + expect(queryByText('rust')).toBeNull(); + }); +}); +``` + +- [ ] **Step 5: Run it — expect failure.** Run: `cd dashboard && npm test -- combobox` → FAIL. + +- [ ] **Step 6: Implement `Combobox.svelte`** + +```svelte + + +
+ (openList = true)} autocomplete="off" spellcheck="false" /> + + {#if openList} + {#if filtered.length} +
    + {#each filtered as o (o)} +
  • choose(o)}>{o}
  • + {/each} +
+ {:else} +
No options available
+ {/if} + {/if} +
+ + +``` + +- [ ] **Step 7: Run the test — expect pass.** Run: `cd dashboard && npm test -- combobox` → PASS. + +- [ ] **Step 8: Commit** + +```bash +git add dashboard/src/components/common dashboard/tests/combobox.test.ts +git commit -m "feat(dashboard): common UI primitives (spinner, modal, collapsible, combobox)" +``` + +### Task 2.2: Reusable button styles + the app shell + +**Files:** +- Create: `dashboard/src/components/common/Button.svelte` +- Create: `dashboard/src/components/shell/Header.svelte` +- Create: `dashboard/src/components/shell/ThemeToggle.svelte` +- Modify: `dashboard/src/App.svelte` + +- [ ] **Step 1: Implement `Button.svelte`** (primary orange / secondary gray / disabled, matching legacy `.btn`) + +```svelte + + + + + +``` + +- [ ] **Step 2: Implement `ThemeToggle.svelte`** (uses the theme store; shows 🌙/☀ + label) + +```svelte + + + + + +``` + +- [ ] **Step 3: Implement `Header.svelte`** — theme-aware logo (swap `serena-logo.svg`/`serena-logo-dark-mode.svg` based on `theme.current`), platinum banner slot, tab nav (`overview` / `logs`), menu dropdown (`Advanced Stats`, `Shutdown Server`). Props: `active: View`, `onnavigate: (v) => void`, `onshutdown: () => void`; a `BannerCarousel` for platinum banners is wired in Phase 7 (leave a placeholder `
` for now). + +```svelte + + +
+
+
+
+
+ +
+ + +``` + +- [ ] **Step 4: Rewrite `App.svelte`** — mount header, switch views, run config/executions/logs pollers per active view, own the shutdown confirm modal. + +```svelte + + +
+
{ /* ShutdownModal wired in Phase 6 */ }} /> +
+ {#if view === 'overview'}
{/if} + {#if view === 'logs'}
{/if} + {#if view === 'stats'}
{/if} +
+
+ + +``` + +- [ ] **Step 5: Type-check + dev smoke.** Run: `cd dashboard && npm run check` (0 errors). Then `npm run dev`, open the printed URL, confirm header + theme toggle render and toggling swaps the logo. + +- [ ] **Step 6: Commit** + +```bash +git add dashboard/src && git commit -m "feat(dashboard): app shell, header, theme toggle, view switching" +``` + +--- + +## Phase 3 — Overview page + +### Task 3.1: Config card + side panels + +**Files:** +- Create: `dashboard/src/components/overview/OverviewPage.svelte` +- Create: `dashboard/src/components/overview/ConfigCard.svelte` +- Create: `dashboard/src/components/overview/ToolUsageBars.svelte` +- Create: `dashboard/src/components/overview/ListPanel.svelte` +- Test: `dashboard/tests/tool-usage-bars.test.ts` + +`ListPanel` is a single reusable `Collapsible`-wrapped list used for Registered Projects, Disabled Tools, Available Modes, and Available Contexts (DRY — one component, four usages). + +- [ ] **Step 1: Implement `ListPanel.svelte`** + +```svelte + + + + {#if items.length} +
    {#each items as item (item)}
  • {item}
  • {/each}
+ {:else} +
None.
+ {/if} +
+ + +``` + +- [ ] **Step 2: Write the failing test `dashboard/tests/tool-usage-bars.test.ts`** (bars sorted by call count, descending, with percentages) + +```ts +import { describe, it, expect } from 'vitest'; +import { render } from '@testing-library/svelte'; +import ToolUsageBars from '../src/components/overview/ToolUsageBars.svelte'; + +describe('ToolUsageBars', () => { + it('renders tools sorted by call count descending', () => { + const { getAllByTestId } = render(ToolUsageBars, { + props: { stats: { a: { num_calls: 1 }, b: { num_calls: 5 } } }, + }); + const names = getAllByTestId('tool-bar-name').map((n) => n.textContent); + expect(names).toEqual(['b', 'a']); + }); +}); +``` + +- [ ] **Step 3: Run it — expect failure.** Run: `cd dashboard && npm test -- tool-usage-bars` → FAIL. + +- [ ] **Step 4: Implement `ToolUsageBars.svelte`** + +```svelte + + +{#if sorted.length} +
+ {#each sorted as [name, s] (name)} +
+ {name} +
+ {s.num_calls} +
+ {/each} +
+{:else} +
No tool usage yet.
+{/if} + + +``` + +- [ ] **Step 5: Run the test — expect pass.** Run: `cd dashboard && npm test -- tool-usage-bars` → PASS. + +- [ ] **Step 6: Implement `ConfigCard.svelte`** — renders from `ResponseConfigOverview`: project name + language(s), context, active modes, encoding, current client, serena version, language badges with remove buttons (emits `onremovelanguage`), an "Add Language" button (emits `onaddlanguage`), an "Edit Global Serena Config" button (emits `oneditconfig`), and the active-tools + memories collapsible lists. Memory entries emit `onopenmemory(name)`; a "Create Memory" button emits `oncreatememory`. Props are the config object plus those callbacks. + +```svelte + + +
+
Project: {data.active_project?.name ?? '—'}
+
Context: {data.context.name ?? '—'}
+
Modes: {data.modes.map((m) => m.name).join(', ') || '—'}
+
Encoding: {data.encoding ?? '—'}
+
Client: {data.current_client ?? '—'}
+
Version: {data.serena_version}
+ +
+ Languages: + {#each data.languages as lang (lang)} + {lang} + + + {/each} + +
+ + +
{#each data.active_tools as t (t)}{t}{/each}
+
+ + {#if data.available_memories} + +
+ {#each data.available_memories as m (m)} + + {/each} +
+ +
+ {/if} + + +
+ + +``` + +- [ ] **Step 7: Implement `OverviewPage.svelte`** — two-column layout reading `config.data`, `stats`, `executions`; left column = ConfigCard + ToolUsageBars + ExecutionsQueue + LastExecution + NewsSection (Phase 7); right column = the four ListPanels + GoldBanners (Phase 7). Wire modal-open callbacks up to App (Phase 6). Executions sub-components are Task 3.2. + +```svelte + + +{#if !d} + +{:else} +
+
+

Current Configuration

+ +
+

Tool Usage

+

Executions Queue

+

Last Execution

+
+
+ p.name)} /> + t.name)} /> + m.name)} /> + c.name)} /> +
+
+{/if} + + +``` + +- [ ] **Step 8: Wire `OverviewPage` into `App.svelte`** (replace the overview placeholder; pass modal callbacks that get filled in Phase 6 — temporary no-ops for now). Type-check + dev smoke against a running backend: confirm config, languages, tool bars, and lists render. + +Run: `cd dashboard && npm run check` → 0 errors. + +- [ ] **Step 9: Commit** + +```bash +git add dashboard/src && git commit -m "feat(dashboard): overview page — config card, tool bars, list panels" +``` + +### Task 3.2: Executions queue + last execution + +**Files:** +- Create: `dashboard/src/components/overview/ExecutionsQueue.svelte` +- Create: `dashboard/src/components/overview/LastExecution.svelte` +- Test: `dashboard/tests/executions.test.ts` + +- [ ] **Step 1: Write the failing test `dashboard/tests/executions.test.ts`** + +```ts +import { describe, it, expect, vi } from 'vitest'; +import { render, getAllByRole } from '@testing-library/svelte'; +import ExecutionsQueue from '../src/components/overview/ExecutionsQueue.svelte'; + +describe('ExecutionsQueue', () => { + it('shows a cancel button only for running items', () => { + const { container } = render(ExecutionsQueue, { + props: { + items: [ + { task_id: 1, is_running: true, name: 'run', finished_successfully: false, logged: false }, + { task_id: 2, is_running: false, name: 'queued', finished_successfully: false, logged: false }, + ], + oncancelexecution: vi.fn(), + }, + }); + expect(getAllByRole(container, 'button').length).toBe(1); + }); +}); +``` + +- [ ] **Step 2: Run it — expect failure.** Run: `cd dashboard && npm test -- executions` → FAIL. + +- [ ] **Step 3: Implement `ExecutionsQueue.svelte`** (pills; running ones get a spinner + cancel button) + +```svelte + + +{#if items.length} +
+ {#each items as ex (ex.task_id)} +
+ {#if ex.is_running}{/if} + {ex.name} + {#if ex.is_running} + + {/if} +
+ {/each} +
+{:else} +
No queued executions.
+{/if} + + +``` + +- [ ] **Step 4: Run the test — expect pass.** Run: `cd dashboard && npm test -- executions` → PASS. + +- [ ] **Step 5: Implement `LastExecution.svelte`** (success/failure status; "None yet." when null) + +```svelte + + +{#if execution} +
+ {execution.name} + {execution.is_running ? 'running' : execution.finished_successfully ? '✓ success' : '✗ failed'} +
+{:else} +
None yet.
+{/if} + + +``` + +- [ ] **Step 6: Type-check.** Run: `cd dashboard && npm run check` → 0 errors. + +- [ ] **Step 7: Commit** + +```bash +git add dashboard/src dashboard/tests/executions.test.ts +git commit -m "feat(dashboard): executions queue and last-execution panels" +``` + +--- + +## Phase 4 — Logs page + +### Task 4.1: Log viewer + toolbar + +**Files:** +- Create: `dashboard/src/components/logs/LogsPage.svelte` +- Create: `dashboard/src/components/logs/LogViewer.svelte` +- Create: `dashboard/src/components/logs/LogToolbar.svelte` +- Test: `dashboard/tests/log-viewer.test.ts` + +- [ ] **Step 1: Write the failing test `dashboard/tests/log-viewer.test.ts`** (renders one styled line per message, level class applied) + +```ts +import { describe, it, expect } from 'vitest'; +import { render } from '@testing-library/svelte'; +import LogViewer from '../src/components/logs/LogViewer.svelte'; + +describe('LogViewer', () => { + it('renders a line per message with a level class', () => { + const { container } = render(LogViewer, { + props: { lines: ['INFO hello', 'ERROR boom'], toolNames: [] }, + }); + const rows = container.querySelectorAll('.log-line'); + expect(rows.length).toBe(2); + expect(rows[1].classList.contains('error')).toBe(true); + }); +}); +``` + +- [ ] **Step 2: Run it — expect failure.** Run: `cd dashboard && npm test -- log-viewer` → FAIL. + +- [ ] **Step 3: Implement `LogViewer.svelte`** (auto-scroll to bottom if user was already at bottom; uses `format.ts`) + +```svelte + + +
+ {#each lines as line, i (i)} +
{@html highlightTools(line, toolNames)}
+ {/each} +
+ + +``` + +- [ ] **Step 4: Run the test — expect pass.** Run: `cd dashboard && npm test -- log-viewer` → PASS. + +- [ ] **Step 5: Implement `LogToolbar.svelte`** — copy / save / clear buttons (the legacy SVG icons), disabled when there are no lines. Copy uses `navigator.clipboard.writeText`; save triggers a Blob download (`serena-logs.txt`); clear calls `logs.clear()`. Each action shows a transient ✓ for ~1s. Props: `lines: string[]`, `onclear: () => void`. + +```svelte + + +
+ + + +
+ + +``` + +- [ ] **Step 6: Implement `LogsPage.svelte`** — loads tool names once on mount (for highlighting), renders `LogToolbar` + `LogViewer` from the `logs` store. + +```svelte + + + logs.clear()} /> + +``` + +- [ ] **Step 7: Wire `LogsPage` into `App.svelte`** (replace the logs placeholder). Type-check + dev smoke: switch to Logs tab, confirm live log streaming, copy/save/clear. + +Run: `cd dashboard && npm run check` → 0 errors. + +- [ ] **Step 8: Commit** + +```bash +git add dashboard/src dashboard/tests/log-viewer.test.ts +git commit -m "feat(dashboard): logs page with live viewer and toolbar" +``` + +--- + +## Phase 5 — Advanced stats page (Frappe Charts) + +### Task 5.1: Chart wrapper + stats page + +**Files:** +- Create: `dashboard/src/components/stats/ChartPanel.svelte` +- Create: `dashboard/src/components/stats/StatsPage.svelte` +- Create: `dashboard/src/components/stats/StatsSummary.svelte` +- Create: `dashboard/src/lib/charts.ts` +- Test: `dashboard/tests/charts.test.ts` + +`charts.ts` transforms `ToolStats` into Frappe Charts data structures (pure functions → unit-testable). `ChartPanel.svelte` is the only place that imports `frappe-charts`. + +- [ ] **Step 1: Write the failing test `dashboard/tests/charts.test.ts`** + +```ts +import { describe, it, expect } from 'vitest'; +import { toPieData, toGroupedBarData } from '../src/lib/charts'; +import type { ToolStats } from '../src/lib/api/types'; + +const stats: ToolStats = { + a: { num_times_called: 2, input_tokens: 10, output_tokens: 5 }, + b: { num_times_called: 8, input_tokens: 40, output_tokens: 20 }, +}; + +describe('charts data', () => { + it('builds pie data for a chosen metric, sorted descending', () => { + const pie = toPieData(stats, 'num_times_called'); + expect(pie.labels).toEqual(['b', 'a']); + expect(pie.datasets[0].values).toEqual([8, 2]); + }); + it('builds grouped bar data with input and output series', () => { + const bar = toGroupedBarData(stats); + expect(bar.labels).toEqual(['b', 'a']); + expect(bar.datasets.map((d) => d.name)).toEqual(['Input Tokens', 'Output Tokens']); + }); +}); +``` + +- [ ] **Step 2: Run it — expect failure.** Run: `cd dashboard && npm test -- charts` → FAIL. + +- [ ] **Step 3: Implement `dashboard/src/lib/charts.ts`** + +```ts +import type { ToolStats, ToolStatEntry } from './api/types'; + +export interface FrappeData { + labels: string[]; + datasets: Array<{ name?: string; values: number[] }>; +} + +function sortedEntries(stats: ToolStats, key: keyof ToolStatEntry): Array<[string, ToolStatEntry]> { + return Object.entries(stats).sort((a, b) => b[1][key] - a[1][key]); +} + +export function toPieData(stats: ToolStats, key: keyof ToolStatEntry): FrappeData { + const entries = sortedEntries(stats, key); + return { labels: entries.map(([n]) => n), datasets: [{ values: entries.map(([, s]) => s[key]) }] }; +} + +export function toGroupedBarData(stats: ToolStats): FrappeData { + const entries = sortedEntries(stats, 'input_tokens'); + return { + labels: entries.map(([n]) => n), + datasets: [ + { name: 'Input Tokens', values: entries.map(([, s]) => s.input_tokens) }, + { name: 'Output Tokens', values: entries.map(([, s]) => s.output_tokens) }, + ], + }; +} +``` + +- [ ] **Step 4: Run the test — expect pass.** Run: `cd dashboard && npm test -- charts` → PASS. + +- [ ] **Step 5: Implement `ChartPanel.svelte`** — wraps Frappe Charts; re-creates the chart when `data`, `type`, or theme changes; pulls colors from CSS variables so charts re-theme on toggle. + +```svelte + + +

{title}

+ + +``` + +- [ ] **Step 6: Implement `StatsSummary.svelte`** — totals table (sum of calls, input tokens, output tokens across tools) from `ToolStats`. + +```svelte + + + + + + + + +
Total calls{totals.calls}
Total input tokens{totals.input}
Total output tokens{totals.output}
+ + +``` + +- [ ] **Step 7: Implement `StatsPage.svelte`** — Refresh/Clear buttons, summary, estimator name, "No stats" message, and the four charts (3 pie + 1 grouped bar) driven by the `stats` store + `charts.ts`. Loads on mount. + +```svelte + + +
+ + +
+ +{#if hasStats} + +
Token estimator: {stats.estimator}
+
+ + + + +
+{:else} +
No tool stats collected yet.
+{/if} + + +``` + +- [ ] **Step 8: Wire `StatsPage` into `App.svelte`** (replace the stats placeholder). Type-check; dev smoke: open Advanced Stats from the menu, confirm charts render in both themes after triggering some tool usage. + +Run: `cd dashboard && npm run check` → 0 errors. + +- [ ] **Step 9: Commit** + +```bash +git add dashboard/src dashboard/tests/charts.test.ts +git commit -m "feat(dashboard): advanced stats page with Frappe Charts" +``` + +--- + +## Phase 6 — Modals and mutations + +### Task 6.1: Modal store + shutdown + cancel-execution modals + +**Files:** +- Create: `dashboard/src/lib/stores/modal.svelte.ts` +- Create: `dashboard/src/components/modals/ShutdownModal.svelte` +- Create: `dashboard/src/components/modals/CancelExecutionModal.svelte` +- Test: `dashboard/tests/modal-store.test.ts` + +The modal store holds a single discriminated-union "active modal" value, so only one modal renders at a time. + +- [ ] **Step 1: Write the failing test `dashboard/tests/modal-store.test.ts`** + +```ts +import { describe, it, expect } from 'vitest'; +import { createModalStore } from '../src/lib/stores/modal.svelte'; + +describe('modal store', () => { + it('opens and closes a single active modal', () => { + const m = createModalStore(); + expect(m.active).toBeNull(); + m.open({ kind: 'shutdown' }); + expect(m.active?.kind).toBe('shutdown'); + m.close(); + expect(m.active).toBeNull(); + }); +}); +``` + +- [ ] **Step 2: Run it — expect failure.** Run: `cd dashboard && npm test -- modal-store` → FAIL. + +- [ ] **Step 3: Implement `dashboard/src/lib/stores/modal.svelte.ts`** + +```ts +export type ModalState = + | { kind: 'shutdown' } + | { kind: 'cancelExecution'; taskId: number } + | { kind: 'addLanguage' } + | { kind: 'removeLanguage'; language: string } + | { kind: 'editMemory'; name: string } + | { kind: 'deleteMemory'; name: string } + | { kind: 'createMemory' } + | { kind: 'editSerenaConfig' }; + +export function createModalStore() { + let active = $state(null); + return { + get active() { return active; }, + open(s: ModalState) { active = s; }, + close() { active = null; }, + }; +} + +export const modal = createModalStore(); +``` + +- [ ] **Step 4: Run the test — expect pass.** Run: `cd dashboard && npm test -- modal-store` → PASS. + +- [ ] **Step 5: Implement `ShutdownModal.svelte`** (confirm → `shutdown()` → close window after 1s) + +```svelte + + + + + + + + +``` + +- [ ] **Step 6: Implement `CancelExecutionModal.svelte`** (the legacy warning text; confirm → `executions.cancel(taskId)`) + +```svelte + + + + + + + + +``` + +- [ ] **Step 7: Commit** + +```bash +git add dashboard/src dashboard/tests/modal-store.test.ts +git commit -m "feat(dashboard): modal store, shutdown and cancel-execution modals" +``` + +### Task 6.2: Language modals + +**Files:** +- Create: `dashboard/src/components/modals/AddLanguageModal.svelte` +- Create: `dashboard/src/components/modals/RemoveLanguageModal.svelte` + +- [ ] **Step 1: Implement `AddLanguageModal.svelte`** — loads `fetchAvailableLanguages()` on mount into a `Combobox`; on Add, shows a `Spinner`, calls `addLanguage(selected)`, then closes and lets the config poller refresh. Shows the legacy hint about language-server download time and the project name. + +```svelte + + + + + + (selected = v)} /> + {#if error}

{error}

{/if} + +
+ + +``` + +- [ ] **Step 2: Implement `RemoveLanguageModal.svelte`** — confirm dialog; calls `removeLanguage(language)`. + +```svelte + + + + + + + + +``` + +- [ ] **Step 3: Type-check + commit** + +Run: `cd dashboard && npm run check` → 0 errors. +```bash +git add dashboard/src && git commit -m "feat(dashboard): add/remove language modals" +``` + +### Task 6.3: Memory modals + name validation (TDD) + +**Files:** +- Create: `dashboard/src/lib/validation.ts` +- Create: `dashboard/src/components/modals/EditMemoryModal.svelte` +- Create: `dashboard/src/components/modals/CreateMemoryModal.svelte` +- Create: `dashboard/src/components/modals/DeleteMemoryModal.svelte` +- Test: `dashboard/tests/validation.test.ts` + +- [ ] **Step 1: Write the failing test `dashboard/tests/validation.test.ts`** (legacy rule: alphanumeric, underscores, slashes for paths) + +```ts +import { describe, it, expect } from 'vitest'; +import { isValidMemoryName } from '../src/lib/validation'; + +describe('memory name validation', () => { + it('accepts names with letters, digits, underscores, and slashes', () => { + expect(isValidMemoryName('architecture/api_design')).toBe(true); + expect(isValidMemoryName('global/java/style_guide')).toBe(true); + }); + it('rejects spaces and other punctuation', () => { + expect(isValidMemoryName('bad name')).toBe(false); + expect(isValidMemoryName('weird!')).toBe(false); + expect(isValidMemoryName('')).toBe(false); + }); +}); +``` + +- [ ] **Step 2: Run it — expect failure.** Run: `cd dashboard && npm test -- validation` → FAIL. + +- [ ] **Step 3: Implement `dashboard/src/lib/validation.ts`** + +```ts +export function isValidMemoryName(name: string): boolean { + return /^[A-Za-z0-9_]+(\/[A-Za-z0-9_]+)*$/.test(name); +} +``` + +- [ ] **Step 4: Run the test — expect pass.** Run: `cd dashboard && npm test -- validation` → PASS. + +- [ ] **Step 5: Implement `EditMemoryModal.svelte`** — loads `getMemory(name)`, large textarea, dirty tracking, inline rename (✎ → input; on save uses `renameMemory(old, new)`), Save uses `saveMemory(name, content)`. Closes on save. + +```svelte + + + + + + + + + +``` + +- [ ] **Step 6: Implement `CreateMemoryModal.svelte`** — name input with the legacy hint; validates with `isValidMemoryName`; on Create calls `saveMemory(name, '')` then transitions to editing (emits `oncreated(name)` so App opens the edit modal). + +```svelte + + + + + + + + + + +``` + +- [ ] **Step 7: Implement `DeleteMemoryModal.svelte`** — confirm; calls `deleteMemory(name)`. + +```svelte + + + + + + + + +``` + +- [ ] **Step 8: Type-check + commit** + +Run: `cd dashboard && npm run check` → 0 errors. +```bash +git add dashboard/src dashboard/tests/validation.test.ts +git commit -m "feat(dashboard): memory edit/create/delete modals with name validation" +``` + +### Task 6.4: Serena config modal + wire all modals into App + +**Files:** +- Create: `dashboard/src/components/modals/EditSerenaConfigModal.svelte` +- Create: `dashboard/src/components/modals/ModalHost.svelte` +- Modify: `dashboard/src/App.svelte` + +- [ ] **Step 1: Implement `EditSerenaConfigModal.svelte`** — loads `getSerenaConfig()`, textarea, the legacy "changes take effect after restart" hint, Save → `saveSerenaConfig(content)`. + +```svelte + + + + + + + + + +``` + +- [ ] **Step 2: Implement `ModalHost.svelte`** — renders the active modal based on `modal.active.kind`, passing the right props and `onclose={() => modal.close()}`. Reads `config.data` for the active project name where needed. For `createMemory`, on `oncreated(name)` it switches the modal to `editMemory`. + +```svelte + + +{#if modal.active} + {#if modal.active.kind === 'shutdown'} + {:else if modal.active.kind === 'cancelExecution'} + {:else if modal.active.kind === 'addLanguage'} + {:else if modal.active.kind === 'removeLanguage'} + {:else if modal.active.kind === 'editMemory'} + {:else if modal.active.kind === 'createMemory'} + modal.open({ kind: 'editMemory', name })} /> + {:else if modal.active.kind === 'deleteMemory'} + {:else if modal.active.kind === 'editSerenaConfig'} + {/if} +{/if} +``` + +- [ ] **Step 3: Modify `App.svelte`** — mount ``; replace the temporary no-op callbacks so `Header` shutdown → `modal.open({ kind: 'shutdown' })`, and `OverviewPage` callbacks open the matching modals (`addLanguage`, `removeLanguage`, `editSerenaConfig`, `editMemory`, `createMemory`, `cancelExecution`). Remove the placeholder onshutdown comment. + +```svelte + + + + + + +``` + +- [ ] **Step 4: Type-check + dev smoke.** Run: `cd dashboard && npm run check` (0 errors). In `npm run dev`: open each modal, exercise add/remove language, create/edit/delete/rename memory, edit serena config, cancel execution, shutdown confirm. + +- [ ] **Step 5: Commit** + +```bash +git add dashboard/src && git commit -m "feat(dashboard): serena-config modal and full modal wiring" +``` + +--- + +## Phase 7 — Banners and news + +### Task 7.1: Banner carousel + +**Files:** +- Create: `dashboard/src/components/banners/BannerCarousel.svelte` +- Create: `dashboard/src/lib/banners.ts` +- Test: `dashboard/tests/banners.test.ts` + +`banners.ts` parses the remote manifest and picks the light/dark variant; it is pure and testable. `BannerCarousel` renders the image + prev/next arrows, random initial index, auto-rotation disabled by default — matching the legacy `BannerRotation`. + +- [ ] **Step 1: Inspect the legacy manifest handling** in the old `src/serena/resources/dashboard/dashboard.js` (`BannerRotation`, lines ~62–240) to copy the exact manifest URL (`https://oraios-software.de/serena-banners/manifest.php`), the JSON shape (entries with light/dark image URLs + link), and the "platinum" vs "gold" target filtering. Record the field names you find before writing `banners.ts`. + +- [ ] **Step 2: Write the failing test `dashboard/tests/banners.test.ts`** (uses the field names confirmed in Step 1; example assumes `{ images: [{ target, light, dark, url }] }`) + +```ts +import { describe, it, expect } from 'vitest'; +import { selectBanners, pickVariant } from '../src/lib/banners'; + +const manifest = { images: [ + { target: 'platinum', light: 'p-l.png', dark: 'p-d.png', url: 'https://a' }, + { target: 'gold', light: 'g-l.png', dark: 'g-d.png', url: 'https://b' }, +] }; + +describe('banners', () => { + it('filters by target', () => { + expect(selectBanners(manifest, 'gold')).toHaveLength(1); + expect(selectBanners(manifest, 'platinum')[0].url).toBe('https://a'); + }); + it('picks the variant for the theme', () => { + const b = selectBanners(manifest, 'platinum')[0]; + expect(pickVariant(b, 'dark')).toBe('p-d.png'); + expect(pickVariant(b, 'light')).toBe('p-l.png'); + }); +}); +``` + +- [ ] **Step 3: Run it — expect failure.** Run: `cd dashboard && npm test -- banners` → FAIL. + +- [ ] **Step 4: Implement `dashboard/src/lib/banners.ts`** (adjust field names to match Step 1 findings) + +```ts +import type { Theme } from './stores/theme.svelte'; + +export interface BannerEntry { target: string; light: string; dark: string; url: string; } +export interface BannerManifest { images: BannerEntry[]; } +export const BANNER_MANIFEST_URL = 'https://oraios-software.de/serena-banners/manifest.php'; + +export function selectBanners(manifest: BannerManifest, target: 'platinum' | 'gold'): BannerEntry[] { + return manifest.images.filter((b) => b.target === target); +} +export function pickVariant(entry: BannerEntry, theme: Theme): string { + return theme === 'dark' ? entry.dark : entry.light; +} +export async function loadManifest(): Promise { + const res = await fetch(BANNER_MANIFEST_URL); + if (!res.ok) return { images: [] }; + return (await res.json()) as BannerManifest; +} +``` + +- [ ] **Step 5: Run the test — expect pass.** Run: `cd dashboard && npm test -- banners` → PASS. + +- [ ] **Step 6: Implement `BannerCarousel.svelte`** — props `target: 'platinum' | 'gold'`; loads manifest on mount, random initial index, prev/next arrows, theme-aware variant, clicking the image opens `entry.url`. If no banners, render nothing. + +```svelte + + +{#if current} + +{/if} + + +``` + +- [ ] **Step 7: Wire `BannerCarousel`** into `Header.svelte` (`target="platinum"` inside `#platinum-banners`) and into `OverviewPage.svelte` right column (`target="gold"`). Type-check. + +Run: `cd dashboard && npm run check` → 0 errors. + +- [ ] **Step 8: Commit** + +```bash +git add dashboard/src dashboard/tests/banners.test.ts +git commit -m "feat(dashboard): platinum and gold banner carousels" +``` + +### Task 7.2: News section + +**Files:** +- Create: `dashboard/src/components/overview/NewsSection.svelte` + +- [ ] **Step 1: Implement `NewsSection.svelte`** — on mount calls `fetchUnreadNews()`; renders each entry's HTML snippet with a "Mark as read" button that calls `markNewsRead(id)` and removes the item locally. Hidden when there is no unread news. News ids are the object keys; values are HTML strings, rendered with `{@html}` (server-trusted content, same as legacy). + +```svelte + + +{#if items.length} +
+

What's New

+ {#each items as [id, html] (id)} +
+
{@html html}
+ +
+ {/each} +
+{/if} + + +``` + +- [ ] **Step 2: Wire `NewsSection`** at the top of the `OverviewPage` left column. Type-check. + +Run: `cd dashboard && npm run check` → 0 errors. + +- [ ] **Step 3: Commit** + +```bash +git add dashboard/src && git commit -m "feat(dashboard): What's New news section" +``` + +--- + +## Phase 8 — Build integration, parity pass, cutover + +### Task 8.1: Produce the build and confirm Flask serves it + +**Files:** +- Modify: committed output under `src/serena/resources/dashboard/` (generated) +- Verify: `src/serena/dashboard.py:223-229` (static serving — read only, no change expected) + +- [ ] **Step 1: Build** + +Run: `cd dashboard && npm run build` +Expected: Vite writes `index.html` + `assets/…` into `src/serena/resources/dashboard/` without deleting the existing icon/logo/png files (`emptyOutDir: false`). + +- [ ] **Step 2: Confirm asset paths are relative.** Open the generated `src/serena/resources/dashboard/index.html` and verify script/style hrefs start with `./assets/` (so they resolve under `/dashboard/`). If they are absolute (`/assets/…`), confirm `base: './'` in `vite.config.ts`. + +- [ ] **Step 3: Run the real backend and load the app.** Start a Serena MCP server with the dashboard enabled (per repo `suggested_commands` memory / README), open `http://localhost:24282/dashboard/`, and verify: overview loads, logs stream, stats charts render, all modals work, banners + news load, theme toggle works in both directions. + +- [ ] **Step 4: Commit the built assets** + +```bash +git add src/serena/resources/dashboard +git commit -m "build(dashboard): commit Svelte build output" +``` + +### Task 8.2: Visual parity pass (both themes) + +**Files:** +- Modify: `dashboard/src/styles/*` and component ` diff --git a/dashboard/src/components/shell/ThemeToggle.svelte b/dashboard/src/components/shell/ThemeToggle.svelte index 73566b6d4..e843b0c5a 100644 --- a/dashboard/src/components/shell/ThemeToggle.svelte +++ b/dashboard/src/components/shell/ThemeToggle.svelte @@ -1,22 +1,40 @@ - diff --git a/dashboard/src/components/stats/ChartPanel.svelte b/dashboard/src/components/stats/ChartPanel.svelte index 74a464910..f68aae80f 100644 --- a/dashboard/src/components/stats/ChartPanel.svelte +++ b/dashboard/src/components/stats/ChartPanel.svelte @@ -1,57 +1,20 @@ - -

{title}

-
+
diff --git a/dashboard/src/components/stats/StatsPage.svelte b/dashboard/src/components/stats/StatsPage.svelte index ae37d1298..a2ea06afc 100644 --- a/dashboard/src/components/stats/StatsPage.svelte +++ b/dashboard/src/components/stats/StatsPage.svelte @@ -1,7 +1,7 @@ - - - - - - - -
Total calls{totals.calls}
Total input tokens{totals.input}
Total output tokens{totals.output}
Total tokens{totals.input + totals.output}
+
+
+ Calls + {formatNumber(totals.calls)} +
+
+ Input tokens + {formatNumber(totals.input)} +
+
+ Output tokens + {formatNumber(totals.output)} +
+
+ Total tokens + {formatNumber(totals.input + totals.output)} +
+
diff --git a/dashboard/src/lib/api/types.ts b/dashboard/src/lib/api/types.ts index 3292a4c4e..e021333cc 100644 --- a/dashboard/src/lib/api/types.ts +++ b/dashboard/src/lib/api/types.ts @@ -84,9 +84,13 @@ export interface ResponseAvailableLanguages { export interface ResponseGetMemory { content: string; memory_name: string; + status?: string; + message?: string; } export interface ResponseGetSerenaConfig { content: string; + status?: string; + message?: string; } export interface QueuedExecution { diff --git a/dashboard/src/lib/charts.ts b/dashboard/src/lib/charts.ts index f9bbc7752..d66fd1aa3 100644 --- a/dashboard/src/lib/charts.ts +++ b/dashboard/src/lib/charts.ts @@ -1,30 +1,82 @@ +import type { ChartConfiguration } from 'chart.js'; +import type { Context as DataLabelsContext } from 'chartjs-plugin-datalabels'; import type { ToolStats, ToolStatEntry } from './api/types'; -export interface FrappeData { - labels: string[]; - datasets: Array<{ name?: string; values: number[] }>; -} +export type ChartSpec = ChartConfiguration<'pie'> | ChartConfiguration<'bar'>; function sortedEntries(stats: ToolStats, key: keyof ToolStatEntry): Array<[string, ToolStatEntry]> { return Object.entries(stats).sort((a, b) => b[1][key] - a[1][key]); } -export function toPieData(stats: ToolStats, key: keyof ToolStatEntry): FrappeData { +// Pie config for one metric. Colours are injected by ChartPanel from CSS vars; +// datalabels render the raw value in bold white on each slice — but only on +// slices ≥4% of the total, so tiny slices don't pile labels into a smudge. +// Legend sits below the pie so all three pies in the row share the same width. +const PIE_LABEL_MIN_FRACTION = 0.04; + +export function pieSpec(stats: ToolStats, key: keyof ToolStatEntry): ChartConfiguration<'pie'> { const entries = sortedEntries(stats, key); return { - labels: entries.map(([n]) => n), - datasets: [{ values: entries.map(([, s]) => s[key]) }], + type: 'pie', + data: { + labels: entries.map(([n]) => n), + datasets: [{ data: entries.map(([, s]) => s[key]) }], + }, + options: { + plugins: { + legend: { display: true, position: 'bottom', labels: {} }, + datalabels: { + display: (ctx: DataLabelsContext) => { + const data = ctx.dataset.data as number[]; + const total = data.reduce((a, b) => a + b, 0); + if (total === 0) return false; + return data[ctx.dataIndex] / total >= PIE_LABEL_MIN_FRACTION; + }, + color: '#ffffff', + font: { weight: 'bold' }, + formatter: (v) => v, + }, + }, + }, }; } -export function toSingleSeriesBar( - stats: ToolStats, - key: 'input_tokens' | 'output_tokens', - name: string, -): FrappeData { - const entries = sortedEntries(stats, key); +// Combined input/output token bar with two y axes (parity with main). +// Sorted by input tokens; output uses the same tool order. Datalabels off. +export function tokensBarSpec(stats: ToolStats): ChartConfiguration<'bar'> { + const entries = sortedEntries(stats, 'input_tokens'); return { - labels: entries.map(([n]) => n), - datasets: [{ name, values: entries.map(([, s]) => s[key]) }], + type: 'bar', + data: { + labels: entries.map(([n]) => n), + datasets: [ + { label: 'Input Tokens', data: entries.map(([, s]) => s.input_tokens), yAxisID: 'y' }, + { label: 'Output Tokens', data: entries.map(([, s]) => s.output_tokens), yAxisID: 'y1' }, + ], + }, + options: { + responsive: true, + layout: { padding: { bottom: 8 } }, + plugins: { + legend: { display: true, labels: {} }, + datalabels: { display: false }, + }, + scales: { + x: { ticks: { maxRotation: 35, padding: 8 } }, + y: { + type: 'linear', + position: 'left', + beginAtZero: true, + title: { display: true, text: 'Input Tokens' }, + }, + y1: { + type: 'linear', + position: 'right', + beginAtZero: true, + title: { display: true, text: 'Output Tokens' }, + grid: { drawOnChartArea: false }, + }, + }, + }, }; } diff --git a/dashboard/src/lib/format.ts b/dashboard/src/lib/format.ts index 5b4f4f2b4..a59fa3067 100644 --- a/dashboard/src/lib/format.ts +++ b/dashboard/src/lib/format.ts @@ -40,3 +40,12 @@ export function highlightTools(text: string, toolNames: string[]): string { return `${escapeHtml(name)}`; }); } + +const numberFormatter = new Intl.NumberFormat('en-US'); + +// Thousands-separated integer formatter. Locale fixed to 'en-US' to keep +// rendering identical across browsers and CI; numbers are the user-facing +// stat totals (Tool Calls, tokens) where commas read naturally. +export function formatNumber(n: number): string { + return numberFormatter.format(n); +} diff --git a/dashboard/src/styles/tokens.css b/dashboard/src/styles/tokens.css index 948970446..d31dc6b57 100644 --- a/dashboard/src/styles/tokens.css +++ b/dashboard/src/styles/tokens.css @@ -15,6 +15,7 @@ --chart-4: #d88c8c; --chart-5: #b39ddb; --chart-6: #e0a458; + --chart-grid: #dddddd; --text-on-accent: #ffffff; --btn-disabled: #adb5bd; --tool-highlight: #fff3bf; @@ -53,6 +54,7 @@ --text-muted: #8b95a1; --border: #2d333b; --border-strong: #3d444d; + --chart-grid: #444444; --btn-disabled: #4a5159; --tool-highlight: #f6c948; --tool-highlight-text: #15181c; diff --git a/dashboard/src/types/chartjs-datalabels.d.ts b/dashboard/src/types/chartjs-datalabels.d.ts new file mode 100644 index 000000000..5baf35373 --- /dev/null +++ b/dashboard/src/types/chartjs-datalabels.d.ts @@ -0,0 +1,5 @@ +// Type-only side-effect import: pulls in chartjs-plugin-datalabels' module +// augmentation so `options.plugins.datalabels` typechecks across the project. +// Erased at runtime (.d.ts files emit nothing); the runtime import lives in +// ChartPanel.svelte. Replaces the old frappe-charts.d.ts ambient shim. +import 'chartjs-plugin-datalabels'; diff --git a/dashboard/src/types/frappe-charts.d.ts b/dashboard/src/types/frappe-charts.d.ts deleted file mode 100644 index 44bc0fb9b..000000000 --- a/dashboard/src/types/frappe-charts.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare module 'frappe-charts' { - export class Chart { - constructor(parent: HTMLElement | string, options: Record); - update(data: { labels: string[]; datasets: Array<{ name?: string; values: number[] }> }): void; - destroy(): void; - } -} diff --git a/dashboard/tests/chart-panel.test.ts b/dashboard/tests/chart-panel.test.ts new file mode 100644 index 000000000..4281491bb --- /dev/null +++ b/dashboard/tests/chart-panel.test.ts @@ -0,0 +1,94 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render } from '@testing-library/svelte'; +import { tick } from 'svelte'; +import ChartPanel from '../src/components/stats/ChartPanel.svelte'; +import { pieSpec } from '../src/lib/charts'; +import { theme } from '../src/lib/stores/theme.svelte'; +import type { ToolStats } from '../src/lib/api/types'; + +// jsdom has no canvas backend — mock Chart.js entirely. +// Use vi.hoisted so the variables are available when vi.mock factories run +// (vi.mock calls are hoisted to the top of the file by Vitest). +const { ChartMock, instance } = vi.hoisted(() => { + const instance = { + data: { labels: [] as unknown[], datasets: [{}, {}] as Record[] }, + options: { plugins: { legend: { labels: {} } }, scales: {} }, + update: vi.fn(), + destroy: vi.fn(), + }; + const ChartMock = vi.fn(() => instance); + return { ChartMock, instance }; +}); + +vi.mock('chart.js/auto', () => ({ default: ChartMock })); +vi.mock('chartjs-plugin-datalabels', () => ({ default: {} })); + +const stats: ToolStats = { a: { num_times_called: 5, input_tokens: 1, output_tokens: 1 } }; + +beforeEach(() => { + ChartMock.mockClear(); + instance.update.mockClear(); + instance.destroy.mockClear(); + // Reset shared mock state so per-test assertions on `instance.data` are isolated. + instance.data.labels = []; + instance.data.datasets = [{}, {}]; +}); + +describe('ChartPanel', () => { + it('constructs a Chart from the spec and renders the title', () => { + const { getByText } = render(ChartPanel, { + props: { title: 'Tool Calls', spec: pieSpec(stats, 'num_times_called') }, + }); + expect(getByText('Tool Calls')).toBeInTheDocument(); + expect(ChartMock).toHaveBeenCalledTimes(1); + const config = (ChartMock.mock.calls[0] as unknown as [unknown, { type: string }])[1]; + expect(config.type).toBe('pie'); + }); + + it('destroys the chart on unmount', () => { + const { unmount } = render(ChartPanel, { + props: { title: 'Tool Calls', spec: pieSpec(stats, 'num_times_called') }, + }); + unmount(); + expect(instance.destroy).toHaveBeenCalled(); + }); + + it('updates in place on spec change without re-constructing the chart', async () => { + const { rerender } = render(ChartPanel, { + props: { title: 'Tool Calls', spec: pieSpec(stats, 'num_times_called') }, + }); + expect(ChartMock).toHaveBeenCalledTimes(1); + const updateCallsBefore = instance.update.mock.calls.length; + const nextStats: ToolStats = { + a: { num_times_called: 5, input_tokens: 1, output_tokens: 1 }, + b: { num_times_called: 9, input_tokens: 2, output_tokens: 2 }, + }; + await rerender({ title: 'Tool Calls', spec: pieSpec(nextStats, 'num_times_called') }); + expect(ChartMock).toHaveBeenCalledTimes(1); // not re-constructed + expect(instance.update.mock.calls.length).toBeGreaterThan(updateCallsBefore); + // Data-effect must actually copy the new labels/data onto the live chart — + // not just call update(). pieSpec sorts descending by metric. + expect(instance.data.labels).toEqual(['b', 'a']); + expect(instance.data.datasets[0].data).toEqual([9, 5]); + }); + + it('re-applies colours when the theme flips', async () => { + render(ChartPanel, { + props: { title: 'Tool Calls', spec: pieSpec(stats, 'num_times_called') }, + }); + const updateCallsBefore = instance.update.mock.calls.length; + const initialTheme = theme.current; + try { + theme.toggle(); + await tick(); + expect(instance.update.mock.calls.length).toBeGreaterThan(updateCallsBefore); + // Theme-effect runs applyTheme(), which writes a colour onto legend labels. + // jsdom's getComputedStyle returns '' for un-stylesheet'd vars → applyTheme + // falls back to the hardcoded default, but it still writes *something*. + const legend = instance.options.plugins.legend.labels as { color?: string }; + expect(legend.color).toBeTruthy(); + } finally { + if (theme.current !== initialTheme) theme.toggle(); + } + }); +}); diff --git a/dashboard/tests/charts.test.ts b/dashboard/tests/charts.test.ts index 5545dd29a..e161e7ce2 100644 --- a/dashboard/tests/charts.test.ts +++ b/dashboard/tests/charts.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { toPieData, toSingleSeriesBar } from '../src/lib/charts'; +import { pieSpec, tokensBarSpec } from '../src/lib/charts'; import type { ToolStats } from '../src/lib/api/types'; const stats: ToolStats = { @@ -7,17 +7,65 @@ const stats: ToolStats = { b: { num_times_called: 8, input_tokens: 40, output_tokens: 20 }, }; -describe('charts data', () => { - it('builds pie data for a chosen metric, sorted descending', () => { - const pie = toPieData(stats, 'num_times_called'); - expect(pie.labels).toEqual(['b', 'a']); - expect(pie.datasets[0].values).toEqual([8, 2]); +describe('pieSpec', () => { + it('builds a pie config for a metric, sorted descending', () => { + const spec = pieSpec(stats, 'num_times_called'); + expect(spec.type).toBe('pie'); + expect(spec.data.labels).toEqual(['b', 'a']); + expect(spec.data.datasets[0].data).toEqual([8, 2]); }); - it('builds a single-series bar for one token metric, sorted descending', () => { - const bar = toSingleSeriesBar(stats, 'output_tokens', 'Output Tokens'); - expect(bar.labels).toEqual(['b', 'a']); - expect(bar.datasets).toHaveLength(1); - expect(bar.datasets[0].name).toBe('Output Tokens'); - expect(bar.datasets[0].values).toEqual([20, 5]); + it('renders the legend below the pie', () => { + const spec = pieSpec(stats, 'input_tokens'); + expect(spec.options?.plugins?.legend?.position).toBe('bottom'); + }); + it('hides datalabels on slices smaller than 4% of the total', () => { + const spec = pieSpec(stats, 'num_times_called'); + const display = spec.options?.plugins?.datalabels?.display; + expect(typeof display).toBe('function'); + // a 5/100 slice (5%) is shown; a 3/100 slice (3%) is hidden. + // Datalabels passes a context with { dataIndex, dataset: { data } }. + const show = (display as (ctx: unknown) => boolean)({ + dataIndex: 0, + dataset: { data: [5, 95] }, + }); + const hide = (display as (ctx: unknown) => boolean)({ + dataIndex: 0, + dataset: { data: [3, 97] }, + }); + expect(show).toBe(true); + expect(hide).toBe(false); + }); +}); + +describe('tokensBarSpec', () => { + it('builds a dual-dataset bar sorted descending by input tokens', () => { + const spec = tokensBarSpec(stats); + expect(spec.type).toBe('bar'); + expect(spec.data.labels).toEqual(['b', 'a']); + expect(spec.data.datasets).toHaveLength(2); + }); + it('assigns input to the left y axis and output to the right y1 axis', () => { + const spec = tokensBarSpec(stats); + const [input, output] = spec.data.datasets; + expect(input.label).toBe('Input Tokens'); + expect(input.data).toEqual([40, 10]); + expect(input.yAxisID).toBe('y'); + expect(output.label).toBe('Output Tokens'); + expect(output.data).toEqual([20, 5]); + expect(output.yAxisID).toBe('y1'); + }); + it('disables datalabels on the bar', () => { + const spec = tokensBarSpec(stats); + expect(spec.options?.plugins?.datalabels?.display).toBe(false); + }); + it('caps x-tick rotation and pads the axis so labels do not crowd', () => { + const spec = tokensBarSpec(stats); + const xScale = spec.options?.scales?.x as + | { ticks?: { maxRotation?: number; padding?: number } } + | undefined; + expect(xScale?.ticks?.maxRotation).toBe(35); + expect(xScale?.ticks?.padding).toBe(8); + const layoutPadding = (spec.options?.layout?.padding ?? {}) as { bottom?: number }; + expect(layoutPadding.bottom).toBe(8); }); }); diff --git a/dashboard/tests/create-memory-modal.test.ts b/dashboard/tests/create-memory-modal.test.ts index da1ed26e6..d60762e89 100644 --- a/dashboard/tests/create-memory-modal.test.ts +++ b/dashboard/tests/create-memory-modal.test.ts @@ -24,4 +24,12 @@ describe('CreateMemoryModal', () => { expect(await screen.findByText('already exists')).toBeInTheDocument(); expect(oncreated).not.toHaveBeenCalled(); }); + + it('renders a "Create Memory" title heading', () => { + stubFetchJson(errBody('nope')); + render(CreateMemoryModal, { + props: { projectName: 'serena', onclose: vi.fn(), oncreated: vi.fn() }, + }); + expect(screen.getByRole('heading', { name: 'Create Memory' })).toBeInTheDocument(); + }); }); diff --git a/dashboard/tests/edit-memory-modal.test.ts b/dashboard/tests/edit-memory-modal.test.ts index f29eb973f..8eafcc5a6 100644 --- a/dashboard/tests/edit-memory-modal.test.ts +++ b/dashboard/tests/edit-memory-modal.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, vi } from 'vitest'; import { render, fireEvent, screen, waitFor } from '@testing-library/svelte'; import EditMemoryModal from '../src/components/modals/EditMemoryModal.svelte'; -import { stubFetchJson } from './helpers'; +import { stubFetchJson, errBody } from './helpers'; describe('EditMemoryModal', () => { it('prompts before discarding unsaved edits and aborts close when declined', async () => { @@ -16,9 +16,17 @@ describe('EditMemoryModal', () => { await waitFor(() => { if ((textarea as HTMLTextAreaElement).value !== 'orig') throw new Error('not loaded yet'); }); + expect(screen.getByRole('button', { name: 'Save' })).not.toBeDisabled(); await fireEvent.input(textarea, { target: { value: 'changed' } }); await fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); expect(confirmSpy).toHaveBeenCalled(); expect(onclose).not.toHaveBeenCalled(); }); + + it('shows an error and disables Save when the memory fails to load', async () => { + stubFetchJson(errBody('memory not found')); + render(EditMemoryModal, { props: { name: 'core', onclose: vi.fn() } }); + expect(await screen.findByRole('alert')).toHaveTextContent('memory not found'); + expect(screen.getByRole('button', { name: 'Save' })).toBeDisabled(); + }); }); diff --git a/dashboard/tests/edit-serena-config-modal.test.ts b/dashboard/tests/edit-serena-config-modal.test.ts index fc545425a..8ad414f74 100644 --- a/dashboard/tests/edit-serena-config-modal.test.ts +++ b/dashboard/tests/edit-serena-config-modal.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, vi } from 'vitest'; import { render, fireEvent, screen, waitFor } from '@testing-library/svelte'; import EditSerenaConfigModal from '../src/components/modals/EditSerenaConfigModal.svelte'; -import { stubFetchJson } from './helpers'; +import { stubFetchJson, errBody } from './helpers'; describe('EditSerenaConfigModal', () => { it('prompts before discarding unsaved edits and aborts close when declined', async () => { @@ -20,4 +20,21 @@ describe('EditSerenaConfigModal', () => { expect(confirmSpy).toHaveBeenCalled(); expect(onclose).not.toHaveBeenCalled(); }); + + it('shows an error and disables Save when the config fails to load', async () => { + stubFetchJson(errBody('Serena config file not found')); + render(EditSerenaConfigModal, { props: { onclose: vi.fn() } }); + expect(await screen.findByRole('alert')).toHaveTextContent('Serena config file not found'); + expect(screen.getByRole('button', { name: 'Save' })).toBeDisabled(); + }); + + it('populates the textarea and enables Save when the config loads', async () => { + stubFetchJson({ content: 'yaml: 1' }); + render(EditSerenaConfigModal, { props: { onclose: vi.fn() } }); + const textarea = await screen.findByRole('textbox'); + await waitFor(() => { + if ((textarea as HTMLTextAreaElement).value !== 'yaml: 1') throw new Error('not loaded yet'); + }); + expect(screen.getByRole('button', { name: 'Save' })).not.toBeDisabled(); + }); }); diff --git a/dashboard/tests/header.test.ts b/dashboard/tests/header.test.ts new file mode 100644 index 000000000..a753c8d36 --- /dev/null +++ b/dashboard/tests/header.test.ts @@ -0,0 +1,14 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import Header from '../src/components/shell/Header.svelte'; +import { stubFetchJson } from './helpers'; + +describe('Header', () => { + it('marks the active view tab with aria-current="page"', () => { + stubFetchJson({}); // BannerCarousel manifest fetch -> empty, no banners + render(Header, { props: { active: 'logs', onnavigate: vi.fn(), onshutdown: vi.fn() } }); + expect(screen.getByRole('button', { name: 'Logs' })).toHaveAttribute('aria-current', 'page'); + expect(screen.getByRole('button', { name: 'Overview' })).not.toHaveAttribute('aria-current'); + expect(screen.getByRole('button', { name: 'Stats' })).not.toHaveAttribute('aria-current'); + }); +}); diff --git a/dashboard/tests/log-viewer.test.ts b/dashboard/tests/log-viewer.test.ts index 6352add79..c1356357a 100644 --- a/dashboard/tests/log-viewer.test.ts +++ b/dashboard/tests/log-viewer.test.ts @@ -11,4 +11,48 @@ describe('LogViewer', () => { expect(rows.length).toBe(2); expect(rows[1].classList.contains('error')).toBe(true); }); + + it('scrolls to the bottom on the first non-empty render', async () => { + const { container } = render(LogViewer, { + props: { lines: ['INFO a', 'INFO b'], toolNames: [] }, + }); + const el = container.querySelector('.log-container') as HTMLElement; + let scrollTopValue = 0; + Object.defineProperty(el, 'scrollHeight', { configurable: true, value: 500 }); + Object.defineProperty(el, 'clientHeight', { configurable: true, value: 100 }); + Object.defineProperty(el, 'scrollTop', { + configurable: true, + get: () => scrollTopValue, + set: (v) => { + scrollTopValue = v; + }, + }); + // Flush the effect's queueMicrotask: setTimeout(0) is a macrotask, and the + // microtask queue always drains before the next macrotask fires, so awaiting + // a setTimeout reliably yields control until our scroll-write has happened. + await new Promise((r) => setTimeout(r, 0)); + expect(scrollTopValue).toBe(500); + }); + + it('does not force-scroll when the user has scrolled up', async () => { + const { container, rerender } = render(LogViewer, { + props: { lines: ['INFO a'], toolNames: [] }, + }); + const el = container.querySelector('.log-container') as HTMLElement; + let scrollTopValue = 0; + Object.defineProperty(el, 'scrollHeight', { configurable: true, value: 500 }); + Object.defineProperty(el, 'clientHeight', { configurable: true, value: 100 }); + Object.defineProperty(el, 'scrollTop', { + configurable: true, + get: () => scrollTopValue, + set: (v) => { + scrollTopValue = v; + }, + }); + await new Promise((r) => setTimeout(r, 0)); // initial scroll fires once + scrollTopValue = 0; // simulate the user scrolling back to the top + await rerender({ lines: ['INFO a', 'INFO b', 'INFO c'], toolNames: [] }); + await new Promise((r) => setTimeout(r, 0)); + expect(scrollTopValue).toBe(0); // sticky logic must NOT yank to the bottom + }); }); diff --git a/dashboard/tests/stats-summary.test.ts b/dashboard/tests/stats-summary.test.ts index 277a4e357..e57bcc7a3 100644 --- a/dashboard/tests/stats-summary.test.ts +++ b/dashboard/tests/stats-summary.test.ts @@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/svelte'; import StatsSummary from '../src/components/stats/StatsSummary.svelte'; describe('StatsSummary', () => { - it('shows a Total tokens row equal to input + output', () => { + it('shows a Total tokens KPI equal to input + output', () => { render(StatsSummary, { props: { stats: { @@ -15,4 +15,18 @@ describe('StatsSummary', () => { expect(screen.getByText('Total tokens')).toBeInTheDocument(); expect(screen.getByTestId('total-tokens')).toHaveTextContent('75'); }); + + it('formats large numbers with thousands separators', () => { + render(StatsSummary, { + props: { + stats: { + a: { num_times_called: 1500, input_tokens: 12_345, output_tokens: 9_876 }, + }, + }, + }); + expect(screen.getByText('1,500')).toBeInTheDocument(); + expect(screen.getByText('12,345')).toBeInTheDocument(); + expect(screen.getByText('9,876')).toBeInTheDocument(); + expect(screen.getByTestId('total-tokens')).toHaveTextContent('22,221'); + }); }); diff --git a/docs/superpowers/plans/2026-05-28-dashboard-bugfixes-f1-f4.md b/docs/superpowers/plans/2026-05-28-dashboard-bugfixes-f1-f4.md new file mode 100644 index 000000000..9f3f739b1 --- /dev/null +++ b/docs/superpowers/plans/2026-05-28-dashboard-bugfixes-f1-f4.md @@ -0,0 +1,552 @@ +# Dashboard Bug-fixes F1–F4 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Fix four UI defects in the Svelte dashboard found during manual testing — silent editor load-errors (F1), logs not tailing on entry (F2), missing active-tab a11y signal (F3), and a missing modal title (F4). + +**Architecture:** Frontend-only changes in `dashboard/`. Reuse existing primitives: the `runMutation` normalizer for the two backend failure channels, the `Modal` component's `error`/`title` props, and the existing scroll math in `LogViewer`. The Flask backend (`src/serena/dashboard.py`) is a frozen contract — untouched. Each fix is one or two files plus its existing Vitest spec. TDD throughout. + +**Tech Stack:** Svelte 5 (runes) + TypeScript, Vite, Vitest + @testing-library/svelte (jsdom). Run all commands from `dashboard/`. + +**Spec:** `docs/superpowers/specs/2026-05-28-dashboard-bugfixes-f1-f4-design.md` + +## File map + +- Modify: `dashboard/src/lib/api/types.ts` — add optional `status?`/`message?` to the two GET-load response interfaces (F1). +- Modify: `dashboard/src/components/modals/EditSerenaConfigModal.svelte` — load via `runMutation`, gate Save (F1). +- Modify: `dashboard/src/components/modals/EditMemoryModal.svelte` — load via `runMutation`, gate Save (F1). +- Modify: `dashboard/src/components/logs/LogViewer.svelte` — initial scroll-to-bottom (F2). +- Modify: `dashboard/src/components/shell/Header.svelte` — `aria-current="page"` on active tab (F3). +- Modify: `dashboard/src/components/modals/CreateMemoryModal.svelte` — add `title="Create Memory"` (F4). +- Modify/Create tests: `tests/edit-serena-config-modal.test.ts`, `tests/edit-memory-modal.test.ts`, `tests/log-viewer.test.ts`, `tests/header.test.ts` (new), `tests/create-memory-modal.test.ts`. +- Regenerate (final task): `src/serena/resources/dashboard/` (`index.html` + hashed `assets/`) via `npm run build`. + +--- + +## Task 1: F1 — EditSerenaConfigModal surfaces load errors, blocks Save + +**Files:** +- Modify: `dashboard/src/lib/api/types.ts:84-90` +- Modify: `dashboard/src/components/modals/EditSerenaConfigModal.svelte` (whole ` + + + + + + +``` + +- [ ] **Step 5: Run the tests to verify they pass** + +Run: `npm test -- edit-serena-config-modal` +Expected: all three tests in the file PASS (the new two plus the existing dirty-discard test, which still loads via the success-shaped stub). + +- [ ] **Step 6: Commit** + +```bash +git add dashboard/src/lib/api/types.ts dashboard/src/components/modals/EditSerenaConfigModal.svelte dashboard/tests/edit-serena-config-modal.test.ts +git commit -m "fix(dashboard): surface config-editor load errors; block Save until loaded (F1) + +Co-Authored-By: Claude Opus 4.7 (1M context) " +``` + +--- + +## Task 2: F1 — EditMemoryModal surfaces load errors, blocks Save + +**Files:** +- Modify: `dashboard/src/lib/api/types.ts:84-87` +- Modify: `dashboard/src/components/modals/EditMemoryModal.svelte` (onMount load + Modal `error` prop + Save button) +- Test: `dashboard/tests/edit-memory-modal.test.ts` + +- [ ] **Step 1: Add the failing test** + +Append this `it` block inside the existing `describe('EditMemoryModal', ...)` in `tests/edit-memory-modal.test.ts`, and add `errBody` to the `./helpers` import (`import { stubFetchJson, errBody } from './helpers';`): + +```ts + it('shows an error and disables Save when the memory fails to load', async () => { + stubFetchJson(errBody('memory not found')); + render(EditMemoryModal, { props: { name: 'core', onclose: vi.fn() } }); + expect(await screen.findByRole('alert')).toHaveTextContent('memory not found'); + expect(screen.getByRole('button', { name: 'Save' })).toBeDisabled(); + }); +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: `npm test -- edit-memory-modal` +Expected: the new test FAILS — current code does `(await getMemory(name)).content ?? ''`, showing no alert and leaving Save enabled. + +- [ ] **Step 3: Add optional soft-error fields to ResponseGetMemory** + +In `dashboard/src/lib/api/types.ts`, change the `ResponseGetMemory` interface (around line 84) to: + +```ts +export interface ResponseGetMemory { + content: string; + memory_name: string; + status?: string; + message?: string; +} +``` + +- [ ] **Step 4: Update EditMemoryModal load + Save gating** + +In `dashboard/src/components/modals/EditMemoryModal.svelte`: + +(a) Add two state declarations immediately after the `let initialContent = $state('');` line (currently line 17): + +```ts + let loaded = $state(false); + let loadError = $state(''); +``` + +(b) Replace the existing `onMount(() => { ... });` block (currently lines 23-31) with: + +```ts + onMount(() => { + void (async () => { + const res = await runMutation(() => getMemory(name)); + if (!res.ok) { + loadError = res.message ?? 'Failed to load memory.'; + return; + } + const loadedContent = res.data?.content ?? ''; + content = loadedContent; + initialContent = loadedContent; + loaded = true; + })(); + }); +``` + +(c) Change the `` opening tag's `error` prop (currently `error={action.error || renameAction.error}`) to include the load error: + +```svelte + +``` + +(d) Change the Save ``) to: + +```svelte + +``` + +- [ ] **Step 5: Run the test to verify it passes** + +Run: `npm test -- edit-memory-modal` +Expected: all tests in the file PASS (the new load-error test plus the existing dirty-discard test, which loads via the success-shaped stub and so sets `loaded = true`). + +- [ ] **Step 6: Commit** + +```bash +git add dashboard/src/lib/api/types.ts dashboard/src/components/modals/EditMemoryModal.svelte dashboard/tests/edit-memory-modal.test.ts +git commit -m "fix(dashboard): surface memory-editor load errors; block Save until loaded (F1) + +Co-Authored-By: Claude Opus 4.7 (1M context) " +``` + +--- + +## Task 3: F2 — LogViewer tails the newest line on first load + +**Files:** +- Modify: `dashboard/src/components/logs/LogViewer.svelte:6-14` (the `$effect`) +- Test: `dashboard/tests/log-viewer.test.ts` + +- [ ] **Step 1: Add the failing tests** + +Append these two `it` blocks inside the existing `describe('LogViewer', ...)` in `tests/log-viewer.test.ts`. jsdom has no layout engine, so the tests install fake `scrollHeight`/`clientHeight` and a `scrollTop` spy on the container before flushing the deferred microtask: + +```ts + it('scrolls to the bottom on the first non-empty render', async () => { + const { container } = render(LogViewer, { + props: { lines: ['INFO a', 'INFO b'], toolNames: [] }, + }); + const el = container.querySelector('.log-container') as HTMLElement; + let scrollTopValue = 0; + Object.defineProperty(el, 'scrollHeight', { configurable: true, value: 500 }); + Object.defineProperty(el, 'clientHeight', { configurable: true, value: 100 }); + Object.defineProperty(el, 'scrollTop', { + configurable: true, + get: () => scrollTopValue, + set: (v) => { + scrollTopValue = v; + }, + }); + // Let the effect's queued microtask run now that the metrics are in place. + await new Promise((r) => setTimeout(r, 0)); + expect(scrollTopValue).toBe(500); + }); + + it('does not force-scroll when the user has scrolled up', async () => { + const { container, rerender } = render(LogViewer, { + props: { lines: ['INFO a'], toolNames: [] }, + }); + const el = container.querySelector('.log-container') as HTMLElement; + let scrollTopValue = 0; + Object.defineProperty(el, 'scrollHeight', { configurable: true, value: 500 }); + Object.defineProperty(el, 'clientHeight', { configurable: true, value: 100 }); + Object.defineProperty(el, 'scrollTop', { + configurable: true, + get: () => scrollTopValue, + set: (v) => { + scrollTopValue = v; + }, + }); + await new Promise((r) => setTimeout(r, 0)); // initial scroll fires once + scrollTopValue = 0; // simulate the user scrolling back to the top + await rerender({ lines: ['INFO a', 'INFO b', 'INFO c'], toolNames: [] }); + await new Promise((r) => setTimeout(r, 0)); + expect(scrollTopValue).toBe(0); // sticky logic must NOT yank to the bottom + }); +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: `npm test -- log-viewer` +Expected: "scrolls to the bottom on the first non-empty render" FAILS — the current effect computes `atBottom` from the (defined) metrics: `500 - 0 - 100 = 400`, which is not `< 40`, so it never scrolls and `scrollTopValue` stays `0`. + +- [ ] **Step 3: Add the one-shot initial scroll to the effect** + +Replace the ` +``` + +Leave the markup (the `
…`) and the ` +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `npm test -- chart-panel` +Expected: PASS (both cases). + +- [ ] **Step 5: Verify typecheck is clean for charts files** + +Run: `npm run check` +Expected: no errors in `charts.ts` or `ChartPanel.svelte`. `StatsPage.svelte` still errors (it imports the removed `toPieData`/`toSingleSeriesBar`) — fixed in Task 4. + +- [ ] **Step 6: Commit** + +```bash +git add dashboard/src/components/stats/ChartPanel.svelte dashboard/tests/chart-panel.test.ts +git commit -m "feat(dashboard): Chart.js ChartPanel with in-place theme/data updates; drop frappe removeChild workaround" +``` + +--- + +## Task 4: Wire StatsPage to the new specs + +**Files:** +- Modify: `dashboard/src/components/stats/StatsPage.svelte` + +- [ ] **Step 1: Update imports and chart markup** + +In `dashboard/src/components/stats/StatsPage.svelte`, change the import line: + +```ts + import { toPieData, toSingleSeriesBar } from '$lib/charts'; +``` + +to: + +```ts + import { pieSpec, tokensBarSpec } from '$lib/charts'; +``` + +Then replace the three pie `ChartPanel`s and the two bar `ChartPanel`s. Replace this block: + +```svelte +
+ + + +
+
+ + +
+``` + +with: + +```svelte +
+ + + +
+
+ +
+``` + +(Leave the ` +``` + +- [ ] **Step 5: Run test to verify both pass** + +Run: `npm test -- stats-summary` + +Expected: 2/2 PASS. + +- [ ] **Step 6: Commit** + +```bash +git add dashboard/src/lib/format.ts dashboard/src/components/stats/StatsSummary.svelte dashboard/tests/stats-summary.test.ts +git commit -m "feat(dashboard): thousands-separated totals in StatsSummary" +``` + +--- + +## Task 2: ChartPanel `height` prop + +**Files:** +- Modify: `dashboard/src/components/stats/ChartPanel.svelte` + +- [ ] **Step 1: Add the `height` prop with a default** + +In `dashboard/src/components/stats/ChartPanel.svelte`, change the props destructure line: + +```ts + let { title, spec }: { title: string; spec: ChartSpec } = $props(); +``` + +to: + +```ts + let { title, spec, height = 240 }: { title: string; spec: ChartSpec; height?: number } = $props(); +``` + +- [ ] **Step 2: Apply the height via inline style** + +In the same file, change: + +```svelte +
+``` + +to: + +```svelte +
+``` + +And remove the `height: 240px;` rule from the `.canvas-wrap` style block so the only remaining rule is `position: relative;`: + +```css + .canvas-wrap { + position: relative; + } +``` + +- [ ] **Step 3: Run the chart-panel tests to verify they still pass** + +Run: `npm test -- chart-panel` + +Expected: 3/3 PASS. (The default 240 keeps existing behaviour identical.) + +- [ ] **Step 4: Commit** + +```bash +git add dashboard/src/components/stats/ChartPanel.svelte +git commit -m "feat(dashboard): ChartPanel accepts an optional height prop" +``` + +--- + +## Task 3: Pie polish — right-side legend + smart datalabels + +**Files:** +- Modify: `dashboard/src/lib/charts.ts` +- Modify: `dashboard/tests/charts.test.ts` + +- [ ] **Step 1: Update the failing tests** + +In `dashboard/tests/charts.test.ts`, replace the entire `describe('pieSpec', ...)` block with: + +```ts +describe('pieSpec', () => { + it('builds a pie config for a metric, sorted descending', () => { + const spec = pieSpec(stats, 'num_times_called'); + expect(spec.type).toBe('pie'); + expect(spec.data.labels).toEqual(['b', 'a']); + expect(spec.data.datasets[0].data).toEqual([8, 2]); + }); + it('renders the legend on the right of the pie', () => { + const spec = pieSpec(stats, 'input_tokens'); + expect(spec.options?.plugins?.legend?.position).toBe('right'); + }); + it('hides datalabels on slices smaller than 4% of the total', () => { + const spec = pieSpec(stats, 'num_times_called'); + const display = spec.options?.plugins?.datalabels?.display; + expect(typeof display).toBe('function'); + // a 5/100 slice (5%) is shown; a 3/100 slice (3%) is hidden. + // Datalabels passes a context with { dataIndex, dataset: { data } }. + const show = (display as (ctx: unknown) => boolean)({ + dataIndex: 0, + dataset: { data: [5, 95] }, + }); + const hide = (display as (ctx: unknown) => boolean)({ + dataIndex: 0, + dataset: { data: [3, 97] }, + }); + expect(show).toBe(true); + expect(hide).toBe(false); + }); +}); +``` + +(Leave the `tokensBarSpec` describe block unchanged — Task 4 updates it.) + +- [ ] **Step 2: Run the tests to verify the new pie cases fail** + +Run: `npm test -- charts` + +Expected: the two new pie cases (`renders the legend on the right of the pie`, `hides datalabels on slices smaller than 4%`) FAIL; the others PASS. + +- [ ] **Step 3: Implement the polish in `pieSpec`** + +In `dashboard/src/lib/charts.ts`, replace the entire `pieSpec` function with: + +```ts +// Pie config for one metric. Colours are injected by ChartPanel from CSS vars; +// datalabels render the raw value in bold white on each slice — but only on +// slices ≥4% of the total, so tiny slices don't pile labels into a smudge. +// Legend is right-anchored so the pie itself gets the full vertical space. +const PIE_LABEL_MIN_FRACTION = 0.04; + +export function pieSpec(stats: ToolStats, key: keyof ToolStatEntry): ChartConfiguration<'pie'> { + const entries = sortedEntries(stats, key); + return { + type: 'pie', + data: { + labels: entries.map(([n]) => n), + datasets: [{ data: entries.map(([, s]) => s[key]) }], + }, + options: { + plugins: { + legend: { display: true, position: 'right', labels: {} }, + datalabels: { + display: (ctx: { dataIndex: number; dataset: { data: number[] } }) => { + const data = ctx.dataset.data; + const total = data.reduce((a, b) => a + b, 0); + if (total === 0) return false; + return data[ctx.dataIndex] / total >= PIE_LABEL_MIN_FRACTION; + }, + color: '#ffffff', + font: { weight: 'bold' }, + formatter: (v) => v, + }, + }, + }, + }; +} +``` + +(Add the `PIE_LABEL_MIN_FRACTION` constant near the top of the file, just above `pieSpec`.) + +- [ ] **Step 4: Run the tests to verify they all pass** + +Run: `npm test -- charts` + +Expected: 6/6 PASS (3 pie cases + the 3 unchanged bar cases). + +- [ ] **Step 5: Bump pie chart panel height to 280 in StatsPage** + +In `dashboard/src/components/stats/StatsPage.svelte`, replace the three pie `ChartPanel` lines: + +```svelte + + + +``` + +with: + +```svelte + + + +``` + +- [ ] **Step 6: Typecheck and commit** + +Run: `npm run check` +Expected: 0 errors. + +```bash +git add dashboard/src/lib/charts.ts dashboard/tests/charts.test.ts dashboard/src/components/stats/StatsPage.svelte +git commit -m "feat(dashboard): pie polish — right legend, hide labels on <4% slices, taller panel" +``` + +--- + +## Task 4: Bar polish — tick rotation, padding, taller panel + +**Files:** +- Modify: `dashboard/src/lib/charts.ts` +- Modify: `dashboard/tests/charts.test.ts` +- Modify: `dashboard/src/components/stats/StatsPage.svelte` + +- [ ] **Step 1: Add the failing test** + +In `dashboard/tests/charts.test.ts`, inside the existing `describe('tokensBarSpec', ...)` block, add this case at the end (after `disables datalabels on the bar`): + +```ts + it('caps x-tick rotation and pads the axis so labels do not crowd', () => { + const spec = tokensBarSpec(stats); + const xScale = spec.options?.scales?.x as { ticks?: { maxRotation?: number; padding?: number } } | undefined; + expect(xScale?.ticks?.maxRotation).toBe(35); + expect(xScale?.ticks?.padding).toBe(8); + const layoutPadding = (spec.options?.layout?.padding ?? {}) as { bottom?: number }; + expect(layoutPadding.bottom).toBe(8); + }); +``` + +- [ ] **Step 2: Run the tests to verify the new case fails** + +Run: `npm test -- charts` + +Expected: the new case FAILS (current `scales.x` is `{}`). + +- [ ] **Step 3: Implement the bar polish in `tokensBarSpec`** + +In `dashboard/src/lib/charts.ts`, in the `tokensBarSpec` function, replace the `options` block. Locate this section: + +```ts + options: { + responsive: true, + plugins: { + legend: { display: true, labels: {} }, + datalabels: { display: false }, + }, + scales: { + x: {}, +``` + +…and replace it with: + +```ts + options: { + responsive: true, + layout: { padding: { bottom: 8 } }, + plugins: { + legend: { display: true, labels: {} }, + datalabels: { display: false }, + }, + scales: { + x: { ticks: { maxRotation: 35, padding: 8 } }, +``` + +(Leave the `y` and `y1` scale blocks unchanged.) + +- [ ] **Step 4: Run the tests to verify they all pass** + +Run: `npm test -- charts` + +Expected: 7/7 PASS. + +- [ ] **Step 5: Bump bar chart panel height to 340 in StatsPage** + +In `dashboard/src/components/stats/StatsPage.svelte`, replace: + +```svelte + +``` + +with: + +```svelte + +``` + +- [ ] **Step 6: Typecheck and commit** + +Run: `npm run check` +Expected: 0 errors. + +```bash +git add dashboard/src/lib/charts.ts dashboard/tests/charts.test.ts dashboard/src/components/stats/StatsPage.svelte +git commit -m "feat(dashboard): bar polish — capped x-tick rotation, tick + layout padding, taller panel" +``` + +--- + +## Task 5: Controls toolbar styling + +**Files:** +- Modify: `dashboard/src/components/stats/StatsPage.svelte` + +- [ ] **Step 1: Style `.controls` as a toolbar** + +In `dashboard/src/components/stats/StatsPage.svelte`, replace the existing `.controls` CSS block: + +```css + .controls { + display: flex; + gap: var(--space-3); + margin-bottom: var(--space-4); + } +``` + +with: + +```css + .controls { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + margin-bottom: var(--space-4); + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow); + } +``` + +(No markup changes; this just wraps the existing buttons in a visually-grouped toolbar consistent with the chart cards.) + +- [ ] **Step 2: Run the full test suite + typecheck** + +Run: `npm test && npm run check` + +Expected: all tests PASS; 0 typecheck errors. + +- [ ] **Step 3: Commit** + +```bash +git add dashboard/src/components/stats/StatsPage.svelte +git commit -m "style(dashboard): group Stats controls into a card-style toolbar" +``` + +--- + +## Task 6: Lint, format, build, regenerate output + +**Files:** +- Modify (generated): `src/serena/resources/dashboard/index.html` + `src/serena/resources/dashboard/assets/` + +- [ ] **Step 1: Lint and format** + +From `dashboard/`: + +```bash +npm run lint +npm run format +``` + +Expected: lint passes; format rewrites any unformatted files. + +- [ ] **Step 2: Full check + tests** + +```bash +npm run check && npm test +``` + +Expected: both PASS. + +- [ ] **Step 3: Build the SPA** + +```bash +npm run build +``` + +Expected: `prebuild` clears the assets dir, Vite writes a fresh hashed bundle into `../src/serena/resources/dashboard/`. No new dependencies; bundle size shouldn't materially change. + +- [ ] **Step 4: Stage ALL changes from repo root** + +```bash +git add -A dashboard src/serena/resources/dashboard +git status +``` + +Expected: staged changes include the regenerated `src/serena/resources/dashboard/index.html` and `assets/*`. Verify nothing is left unstaged. + +- [ ] **Step 5: Commit** + +```bash +git commit -m "build(dashboard): regenerate bundle after Stats polish" +``` + +- [ ] **Step 6: Smoke check (optional, recommended)** + +Restart the emulator script (`/tmp/emulate_dashboard.py`) — see the `run-dashboard-emulate-tool-calls` memory — open the Stats tab, and confirm: + +- Summary totals show commas (e.g. `12,345`). +- Pies have legends on the right; small slices (e.g. `write_memory` at ~3/114 = ~2.6%) have NO datalabel; mid/large slices still do. +- Bar chart x-axis labels sit clear of the panel edge; rotation is moderate (~35°). +- Refresh / Clear buttons sit in a card-style toolbar consistent with the chart cards. +- Theme toggle still recolours legends, axes, grid, and ticks without console errors. + +--- + +## Self-Review Notes (verified against the user's request) + +- **Item 2 (pie polish):** right legend (`pieSpec` options + assertion in test), smart datalabels (callback hides slices < 4%), taller panel (height=280 in StatsPage). ✅ +- **Item 3 (bar polish):** taller (height=340), `maxRotation: 35`, `ticks.padding: 8`, `layout.padding.bottom: 8`. ✅ +- **Item 4 (number formatting + controls spacing):** `Intl.NumberFormat` via `formatNumber` in `format.ts`, applied to all four totals; controls become a card-style toolbar. ✅ +- **Out of scope:** KPI cards (item 1) NOT included per user choice. No new dependencies; no API/store changes; backend untouched. +- **Type consistency:** `ChartPanel.svelte`'s new `height` prop is optional with a default (240), so the `chartjs-swap` chart-panel tests still pass without modification. The new pie `display` callback signature matches Chart.js datalabels' actual context shape (`{ dataIndex, dataset: { data } }`), kept narrow via `unknown`-typed cast in the test for stability. +- **No placeholders:** every step has explicit before/after code or commands. diff --git a/docs/superpowers/specs/2026-05-28-dashboard-bugfixes-f1-f4-design.md b/docs/superpowers/specs/2026-05-28-dashboard-bugfixes-f1-f4-design.md new file mode 100644 index 000000000..067bc015b --- /dev/null +++ b/docs/superpowers/specs/2026-05-28-dashboard-bugfixes-f1-f4-design.md @@ -0,0 +1,197 @@ +# Serena Dashboard — Bug-fix round F1–F4 (Design Spec) + +**Date:** 2026-05-28 +**Status:** Approved for planning +**Author:** Brainstorming session (Pavlo Basanets + Claude) +**Source:** Findings from the manual Playwright UI test pass — see `dashboard-ui-test-findings.md`. + +## 1. Summary + +Fix four defects found during live UI testing of the Svelte 5 dashboard. Two are +real bugs (P2): editor modals silently swallow load failures (F1), and the Logs +viewer opens at the oldest line instead of tailing the newest (F2). Two are +polish/a11y items (P3): the view tabs lack an accessible "current view" signal +(F3), and the Create Memory modal has no title heading (F4). + +All changes are **frontend-only**. The Flask backend in `src/serena/dashboard.py` +remains a **frozen contract** — no endpoint names, shapes, ports, or host-header +checks change. Each fix is isolated to one or two components plus its existing +test file. + +## 2. Goals & Non-Goals + +### Goals +- Editor modals (`EditSerenaConfigModal`, `EditMemoryModal`) surface load + failures and prevent Save until content has actually loaded. +- The Logs viewer tails the newest line on first entry, preserving the existing + "sticky only when at bottom" behavior for subsequent updates. +- The active view tab is announced to assistive tech. +- The Create Memory modal has a title heading, consistent with the other modals. +- Net-new / extended test coverage for each fix. + +### Non-Goals +- No backend changes; no endpoint contract changes. +- No YAML pre-save validation, no log level-filter/search, no hash routing — those + remain in the improvement-suggestions backlog, out of scope here. +- No change to `runMutation` itself, the `Modal` primitive's API, or theming. + +## 3. The Fixes + +### F1 — Editor modals surface load (GET) errors; Save blocked until loaded + +**Problem.** The load path of `EditSerenaConfigModal.load()` calls +`getSerenaConfig()` directly. `getJson` only throws `ApiError` on non-2xx; a +soft-error (`HTTP 200 {status:'error', message}`) is *not* thrown, so `.content` +is `undefined`, the textarea binds blank, **no error is shown, and Save stays +enabled** — risking an overwrite of the global config with empty content. +`EditMemoryModal` has the same swallow, mitigated only by a `?? ''` fallback (no +error surfaced either). + +**Decision (approved).** Apply the fix to **both** editor modals. + +**Changes.** + +1. **`src/lib/api/types.ts`** — add the optional soft-error fields to the two + GET-load response types. These GETs legitimately return *either* the success + shape *or* `{status:'error', message}`; modeling both also satisfies + `runMutation`'s `T extends { status?: string; message?: string }` generic + constraint: + ```ts + export interface ResponseGetMemory { + content: string; + memory_name: string; + status?: string; + message?: string; + } + export interface ResponseGetSerenaConfig { + content: string; + status?: string; + message?: string; + } + ``` + +2. **`src/components/modals/EditSerenaConfigModal.svelte`** and + **`src/components/modals/EditMemoryModal.svelte`** — route the load through the + existing `runMutation` and gate Save on a `loaded` flag: + ```ts + let loaded = $state(false); + let loadError = $state(''); + async function load() { + // getSerenaConfig() here; getMemory(name) in the memory modal + const res = await runMutation(() => getSerenaConfig()); + if (!res.ok) { + loadError = res.message ?? 'Failed to load configuration.'; + return; + } + const c = res.data?.content ?? ''; + content = c; + initialContent = c; + loaded = true; + } + ``` + - `Modal` receives `error={loadError || action.error}` (reuses the existing + `role="alert"` error slot — no `Modal` API change). + - Save button: `disabled={!loaded || action.busy}`. + - The memory modal's `(await getMemory(name)).content ?? ''` swallow and the + config modal's no-guard `content = loaded` assignment are both replaced by + the above. + +**Acceptance.** +- When the load endpoint returns the soft-error channel, the modal shows the + backend `message` inline and the **Save button is disabled**. +- When the load succeeds, the textarea is populated and Save is enabled. +- `runMutation` is unchanged; no `fetch` is added to a component. + +### F2 — Logs viewer scrolls to bottom on first load (regression vs `main`) + +**Problem.** `LogViewer.svelte`'s `$effect` only scrolls when *already* within +40px of the bottom. On entry, the logs poller's first +`/get_log_messages {start_idx:0}` returns the whole backlog rendered in one pass, +so `scrollTop` is 0 and the check is false — the viewer stays pinned to the +oldest line. The legacy jQuery dashboard scrolled to bottom **unconditionally on +initial load** (`dashboard.js:1277`) and applied the `wasAtBottom`-gated sticky +scroll only to **subsequent** polls (`dashboard.js:1311–1327`); the rewrite +collapsed both into the single gated effect. + +**Change — `src/components/logs/LogViewer.svelte`:** +```ts +let didInitialScroll = false; +$effect(() => { + void lines.length; // re-run when new lines arrive + if (!el || lines.length === 0) return; + if (!didInitialScroll) { + didInitialScroll = true; + queueMicrotask(() => { if (el) el.scrollTop = el.scrollHeight; }); + return; + } + const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 40; + if (atBottom) queueMicrotask(() => { if (el) el.scrollTop = el.scrollHeight; }); +}); +``` + +**Acceptance.** +- On first non-empty render the viewer is scrolled to the bottom (newest line). +- After that, new lines pin to the bottom only when the user is already at the + bottom; scrolling up is not interrupted (existing behavior preserved). + +### F3 — `aria-current="page"` on the active view tab + +**Problem.** The Overview / Logs / Stats buttons (in `Header.svelte`, inside a +`
');function Us(e,t){at(t,!0);let n=Xt(t,"height",3,240),i=B(null),s=null;function o(d,g){return typeof document>"u"?g:getComputedStyle(document.documentElement).getPropertyValue(d).trim()||g}function r(){return[o("--accent","#eaa45d"),o("--chart-2","#6aa3d8"),o("--chart-3","#7fb77e"),o("--chart-4","#d88c8c"),o("--chart-5","#b39ddb"),o("--chart-6","#e0a458")]}function a(){var _,x;if(!s)return;const d=r(),g=o("--text-primary","#1f2328"),m=o("--chart-grid","#dddddd"),p=(s.data.labels??[]).map((y,w)=>d[w%d.length]);if(t.spec.type==="pie")s.data.datasets[0].backgroundColor=p;else{const y=s.data.datasets[0],w=s.data.datasets[1];y.backgroundColor=p.map(S=>S+"80"),y.borderColor=p,y.borderWidth=2,w.backgroundColor=p}const v=s.options;if((x=(_=v.plugins)==null?void 0:_.legend)!=null&&x.labels&&(v.plugins.legend.labels.color=g),v.scales)for(const y of Object.values(v.scales))y&&(y.ticks={...y.ticks,color:g},y.grid={...y.grid,color:m},y.title&&(y.title.color=g));s.update()}Vn(()=>{if(!b(i))return;const d=b(i),g=Ge(()=>new Ve(d,{...t.spec,plugins:[hx]}));return s=g,Ge(()=>a()),()=>{s=null,g.destroy()}}),Vn(()=>{const d=t.spec;s&&(s.data.labels=d.data.labels??[],d.data.datasets.forEach((g,m)=>{s.data.datasets[m]&&(s.data.datasets[m].data=g.data)}),a())}),Vn(()=>{Yn.current,s&&a()});var l=ux(),c=C(l),h=C(c),u=T(c,2),f=C(u);Oo(f,d=>L(i,d),()=>b(i)),G(()=>{q(h,t.title),Gc(u,`height: ${n()??""}px`)}),A(e,l),lt()}var fx=R('
Calls
Input tokens
Output tokens
Total tokens
');function dx(e,t){at(t,!0);const n=it(()=>Object.values(t.stats).reduce((p,v)=>({calls:p.calls+v.num_times_called,input:p.input+v.input_tokens,output:p.output+v.output_tokens}),{calls:0,input:0,output:0}));var i=fx(),s=C(i),o=T(C(s),2),r=C(o),a=T(s,2),l=T(C(a),2),c=C(l),h=T(a,2),u=T(C(h),2),f=C(u),d=T(h,2),g=T(C(d),2),m=C(g);G((p,v,_,x)=>{q(r,p),q(c,v),q(f,_),q(m,x)},[()=>Cs(b(n).calls),()=>Cs(b(n).input),()=>Cs(b(n).output),()=>Cs(b(n).input+b(n).output)]),A(e,i),lt()}var gx=R('
',1),px=R('
No tool stats collected yet.
'),mx=R('
',1);function vx(e,t){at(t,!0);const n=it(()=>Object.keys(Se.stats).length>0);Ze(()=>{Se.refresh()});var i=mx(),s=rt(i),o=C(s);qt(o,{onclick:()=>Se.refresh(),children:(h,u)=>{var f=yt("Refresh Stats");A(h,f)},$$slots:{default:!0}});var r=T(o,2);qt(r,{onclick:()=>Se.clear(),children:(h,u)=>{var f=yt("Clear Stats");A(h,f)},$$slots:{default:!0}});var a=T(s,2);{var l=h=>{var u=gx(),f=rt(u);dx(f,{get stats(){return Se.stats}});var d=T(f,2),g=C(d),m=T(d,2),p=C(m);{let w=it(()=>$o(Se.stats,"num_times_called"));Us(p,{title:"Tool Calls",height:360,get spec(){return b(w)}})}var v=T(p,2);{let w=it(()=>$o(Se.stats,"input_tokens"));Us(v,{title:"Input Tokens",height:360,get spec(){return b(w)}})}var _=T(v,2);{let w=it(()=>$o(Se.stats,"output_tokens"));Us(_,{title:"Output Tokens",height:360,get spec(){return b(w)}})}var x=T(m,2),y=C(x);{let w=it(()=>Rg(Se.stats));Us(y,{title:"Token Usage (by tool)",height:460,get spec(){return b(w)}})}G(()=>q(g,`Token estimator: ${Se.estimator??""}`)),A(h,u)},c=h=>{var u=px();A(h,u)};tt(a,h=>{b(n)?h(l):h(c,-1)})}A(e,i),lt()}function _x(){let e=B(null);return{get active(){return b(e)},open(t){L(e,t,!0)},close(){L(e,null)}}}const pe=_x();var bx=R("

"),xx=R(''),yx=R('');function ks(e,t){at(t,!0);let n=Xt(t,"open",3,!1),i=Xt(t,"title",3,""),s=Xt(t,"error",3,""),o=B(null),r=null;Vn(()=>{n()&&b(o)&&(r=document.activeElement,b(o).focus())}),Jc(()=>r==null?void 0:r.focus());function a(u){if(u.key==="Escape"){t.onclose();return}if(u.key!=="Tab"||!n()||!b(o))return;const f=b(o).querySelectorAll('a[href], button:not([disabled]), input, textarea, select, [tabindex]:not([tabindex="-1"])');if(f.length===0)return;const d=f[0],g=f[f.length-1],m=document.activeElement;u.shiftKey&&m===d?(u.preventDefault(),g.focus()):!u.shiftKey&&m===g&&(u.preventDefault(),d.focus())}var l=zt();$c("keydown",yr,a);var c=rt(l);{var h=u=>{var f=yx(),d=C(f),g=C(d),m=T(g,2);{var p=y=>{var w=bx(),S=C(w);G(()=>q(S,i())),A(y,w)};tt(m,y=>{i()&&y(p)})}var v=T(m,2);{var _=y=>{var w=xx(),S=C(w);G(()=>q(S,s())),A(y,w)};tt(v,y=>{s()&&y(_)})}var x=T(v,2);bs(x,()=>t.children),Oo(d,y=>L(o,y),()=>b(o)),ut("click",f,function(...y){var w;(w=t.onclose)==null||w.apply(this,y)}),ut("keydown",f,a),ut("click",d,y=>y.stopPropagation()),ut("keydown",d,y=>{a(y),y.stopPropagation()}),ut("click",g,function(...y){var w;(w=t.onclose)==null||w.apply(this,y)}),A(u,f)};tt(c,u=>{n()&&u(h)})}A(e,l),lt()}Me(["click","keydown"]);function ki(){let e=B(!1),t=B("");return{get busy(){return b(e)},get error(){return b(t)},clearError(){L(t,"")},async run(n,i){L(e,!0),L(t,"");const s=await n();return L(e,!1),s.ok?(i(),s):(L(t,s.message??"",!0),s)}}}var wx=R(' ',1);function Bo(e,t){at(t,!0);let n=Xt(t,"title",3,""),i=Xt(t,"confirmLabel",3,"OK"),s=Xt(t,"variant",3,"primary");const o=ki();ks(e,{open:!0,get title(){return n()},get error(){return o.error},get onclose(){return t.onclose},children:(r,a)=>{var l=wx(),c=rt(l),h=C(c);bs(h,()=>t.children);var u=T(c,2),f=C(u);qt(f,{variant:"secondary",get onclick(){return t.onclose},children:(g,m)=>{var p=yt("Cancel");A(g,p)},$$slots:{default:!0}});var d=T(f,2);qt(d,{get variant(){return s()},get disabled(){return o.busy},onclick:()=>o.run(t.onconfirm,t.onclose),children:(g,m)=>{var p=zt(),v=rt(p);{var _=y=>{xs(y)},x=y=>{var w=yt();G(()=>q(w,i())),A(y,w)};tt(v,y=>{o.busy?y(_):y(x,-1)})}A(g,p)},$$slots:{default:!0}}),A(r,l)},$$slots:{default:!0}}),lt()}function kx(e,t){at(t,!0);async function n(){const i=await ye(()=>ud());return i.ok&&setTimeout(()=>window.close(),1e3),i}Bo(e,{title:"Shutdown Server",confirmLabel:"Shutdown",variant:"danger",onconfirm:n,get onclose(){return t.onclose},children:(i,s)=>{var o=yt("Shut down the Serena server?");A(i,o)},$$slots:{default:!0}}),lt()}function Mx(e,t){at(t,!0);function n(){Oe.clearCancelError(),t.onclose()}Bo(e,{onconfirm:()=>Oe.cancel(t.execution),onclose:n,children:(i,s)=>{var o=yt(`Are you sure? The execution will continue running until timeout, it will simply no longer be in + the queue. Abandoning a running execution is only advised as a measure for unblocking Serena.`);A(i,o)},$$slots:{default:!0}}),lt()}var Sx=R('
  • '),Px=R('
      '),Cx=R('
      No options available
      '),Ax=R('
      ');function Dx(e,t){at(t,!0);let n=Xt(t,"value",3,""),i=Xt(t,"placeholder",3,"Type to filter…"),s=B(Tt(n())),o=B(!1),r=B(null);const a=it(()=>t.options.filter(m=>m.toLowerCase().includes(b(s).toLowerCase())));function l(m){L(s,m,!0),L(o,!1),t.onselect(m)}function c(m){var v;const p=m.relatedTarget;p&&((v=b(r))!=null&&v.contains(p))||L(o,!1)}function h(m,p){(m.key==="Enter"||m.key===" ")&&(m.preventDefault(),l(p))}var u=Ax(),f=C(u),d=T(f,4);{var g=m=>{var p=zt(),v=rt(p);{var _=y=>{var w=Px();le(w,20,()=>b(a),S=>S,(S,M)=>{var P=Sx(),E=C(P);G(()=>{Pt(P,"aria-selected",M===b(s)),q(E,M)}),ut("click",P,()=>l(M)),ut("keydown",P,I=>h(I,M)),A(S,P)}),A(y,w)},x=y=>{var w=Cx();A(y,w)};tt(v,y=>{b(a).length?y(_):y(x,-1)})}A(m,p)};tt(d,m=>{b(o)&&m(g)})}Oo(u,m=>L(r,m),()=>b(r)),G(()=>Pt(f,"placeholder",i())),ut("focusout",u,c),$c("focus",f,()=>L(o,!0)),ut("input",f,()=>L(o,!0)),es(f,()=>b(s),m=>L(s,m)),A(e,u),lt()}Me(["focusout","input","click","keydown"]);var Tx=R(` `,1);function Ox(e,t){at(t,!0);let n=B(Tt([])),i=B("");const s=ki();Ze(()=>{(async()=>L(n,(await fd()).languages,!0))()});function o(){b(i)&&s.run(()=>ye(()=>dd(b(i))),t.onclose)}ks(e,{open:!0,title:"Add Language",get error(){return s.error},get onclose(){return t.onclose},children:(r,a)=>{var l=Tx(),c=rt(l),h=T(C(c)),u=C(h),f=T(c,4);Dx(f,{get options(){return b(n)},get value(){return b(i)},onselect:p=>L(i,p,!0)});var d=T(f,2),g=C(d);qt(g,{variant:"secondary",get onclick(){return t.onclose},children:(p,v)=>{var _=yt("Cancel");A(p,_)},$$slots:{default:!0}});var m=T(g,2);{let p=it(()=>s.busy||!b(i));qt(m,{get disabled(){return b(p)},onclick:o,children:(v,_)=>{var x=zt(),y=rt(x);{var w=M=>{xs(M)},S=M=>{var P=yt("Add Language");A(M,P)};tt(y,M=>{s.busy?M(w):M(S,-1)})}A(v,x)},$$slots:{default:!0}})}G(()=>q(u,t.projectName)),A(r,l)},$$slots:{default:!0}}),lt()}var Ex=R("Remove language from configuration?",1);function Lx(e,t){at(t,!0),Bo(e,{onconfirm:()=>ye(()=>gd(t.language)),get onclose(){return t.onclose},children:(n,i)=>{var s=Ex(),o=T(rt(s)),r=C(o);G(()=>q(r,t.language)),A(n,s)},$$slots:{default:!0}}),lt()}function ru(e){return/^[A-Za-z0-9_]+(\/[A-Za-z0-9_]+)*$/.test(e)}function au(e){return!e||window.confirm("You have unsaved changes. Discard them?")}var Rx=R(' ',1),Ix=R(' ',1),Fx=R(' ',1);function zx(e,t){at(t,!0);let n=B(Tt(Ge(()=>t.name))),i=B(""),s=B(""),o=B(!1),r=B(""),a=B(!1),l=B(Tt(Ge(()=>t.name)));const c=it(()=>b(i)!==b(s)),h=ki(),u=ki();async function f(){var _;const p=await ye(()=>pd(t.name));if(!p.ok){L(r,p.message??"Failed to load memory.",!0);return}const v=((_=p.data)==null?void 0:_.content)??"";L(i,v,!0),L(s,v,!0),L(o,!0)}Ze(()=>{f()});function d(){au(b(c))&&t.onclose()}function g(){h.run(()=>ye(()=>Qc(b(n),b(i))),t.onclose)}function m(){if(!ru(b(l))||b(l)===b(n)){L(a,!1);return}u.run(()=>ye(()=>vd(b(n),b(l))),()=>{L(n,b(l),!0),L(a,!1)})}{let p=it(()=>b(r)||h.error||u.error);ks(e,{open:!0,get error(){return b(p)},onclose:d,children:(v,_)=>{var x=Fx(),y=rt(x),w=T(C(y));{var S=O=>{var F=Rx(),V=rt(F),z=T(V,2);G(()=>z.disabled=u.busy),ut("keydown",V,H=>H.key==="Enter"&&m()),es(V,()=>b(l),H=>L(l,H)),ut("click",z,m),A(O,F)},M=O=>{var F=Ix(),V=rt(F),z=C(V),H=T(V,2);G(()=>q(z,b(n))),ut("click",H,()=>{L(a,!0),L(l,b(n),!0)}),A(O,F)};tt(w,O=>{b(a)?O(S):O(M,-1)})}var P=T(y,2),E=T(P,2),I=C(E);qt(I,{variant:"secondary",onclick:d,children:(O,F)=>{var V=yt("Cancel");A(O,V)},$$slots:{default:!0}});var j=T(I,2);{let O=it(()=>!b(o)||h.busy);qt(j,{get disabled(){return b(O)},onclick:g,children:(F,V)=>{var z=yt("Save");A(F,z)},$$slots:{default:!0}})}es(P,()=>b(i),O=>L(i,O)),A(v,x)},$$slots:{default:!0}})}lt()}Me(["keydown","click"]);var Nx=R(` `,1);function Bx(e,t){at(t,!0);let n=B("");const i=it(()=>ru(b(n))),s=ki();function o(){b(i)&&s.run(()=>ye(()=>Qc(b(n),"")),()=>t.oncreated(b(n)))}ks(e,{open:!0,title:"Create Memory",get error(){return s.error},get onclose(){return t.onclose},children:(r,a)=>{var l=Nx(),c=rt(l),h=T(C(c)),u=C(h),f=T(c,4),d=T(f,2),g=C(d);qt(g,{variant:"secondary",get onclick(){return t.onclose},children:(p,v)=>{var _=yt("Cancel");A(p,_)},$$slots:{default:!0}});var m=T(g,2);{let p=it(()=>!b(i)||s.busy);qt(m,{get disabled(){return b(p)},onclick:o,children:(v,_)=>{var x=zt(),y=rt(x);{var w=M=>{xs(M)},S=M=>{var P=yt("Create");A(M,P)};tt(y,M=>{s.busy?M(w):M(S,-1)})}A(v,x)},$$slots:{default:!0}})}G(()=>q(u,t.projectName)),es(f,()=>b(n),p=>L(n,p)),A(r,l)},$$slots:{default:!0}}),lt()}var jx=R("Delete memory ?",1);function Vx(e,t){at(t,!0),Bo(e,{variant:"danger",onconfirm:()=>ye(()=>md(t.name)),get onclose(){return t.onclose},children:(n,i)=>{var s=jx(),o=T(rt(s)),r=C(o);G(()=>q(r,t.name)),A(n,s)},$$slots:{default:!0}}),lt()}var Wx=R(' ',1);function Hx(e,t){at(t,!0);let n=B(""),i=B(""),s=B(!1),o=B("");const r=it(()=>b(n)!==b(i)),a=ki();async function l(){var d;const u=await ye(()=>_d());if(!u.ok){L(o,u.message??"Failed to load configuration.",!0);return}const f=((d=u.data)==null?void 0:d.content)??"";L(n,f,!0),L(i,f,!0),L(s,!0)}Ze(()=>{l()});function c(){au(b(r))&&t.onclose()}function h(){a.run(()=>ye(()=>bd(b(n))),t.onclose)}{let u=it(()=>b(o)||a.error);ks(e,{open:!0,title:"Global Serena Configuration",get error(){return b(u)},onclose:c,children:(f,d)=>{var g=Wx(),m=T(rt(g),2),p=T(m,2),v=C(p);qt(v,{variant:"secondary",onclick:c,children:(x,y)=>{var w=yt("Cancel");A(x,w)},$$slots:{default:!0}});var _=T(v,2);{let x=it(()=>!b(s)||a.busy);qt(_,{get disabled(){return b(x)},onclick:h,children:(y,w)=>{var S=yt("Save");A(y,S)},$$slots:{default:!0}})}es(m,()=>b(n),x=>L(n,x)),A(f,g)},$$slots:{default:!0}})}lt()}function Yx(e,t){at(t,!0);const n=it(()=>{var l,c;return String(((c=(l=go.data)==null?void 0:l.active_project)==null?void 0:c.name)??"")}),i=it(()=>pe.active),s=()=>pe.close();var o=zt(),r=rt(o);{var a=l=>{var c=zt(),h=rt(c);{var u=x=>{kx(x,{onclose:s})},f=x=>{Mx(x,{get execution(){return b(i).execution},onclose:s})},d=x=>{Ox(x,{get projectName(){return b(n)},onclose:s})},g=x=>{Lx(x,{get language(){return b(i).language},onclose:s})},m=x=>{var y=zt(),w=rt(y);Ef(w,()=>b(i).name,S=>{zx(S,{get name(){return b(i).name},onclose:s})}),A(x,y)},p=x=>{Bx(x,{get projectName(){return b(n)},onclose:s,oncreated:y=>pe.open({kind:"editMemory",name:y})})},v=x=>{Vx(x,{get name(){return b(i).name},onclose:s})},_=x=>{Hx(x,{onclose:s})};tt(h,x=>{b(i).kind==="shutdown"?x(u):b(i).kind==="cancelExecution"?x(f,1):b(i).kind==="addLanguage"?x(d,2):b(i).kind==="removeLanguage"?x(g,3):b(i).kind==="editMemory"?x(m,4):b(i).kind==="createMemory"?x(p,5):b(i).kind==="deleteMemory"?x(v,6):b(i).kind==="editSerenaConfig"&&x(_,7)})}A(l,c)};tt(r,l=>{b(i)&&l(a)})}A(e,o),lt()}var Ux=R('
      '),Xx=R('
      '),qx=R('
      '),$x=R('
      ',1);function Kx(e,t){at(t,!0);let n=B("overview");Vn(()=>{var S,M;document.title=Ad((M=(S=go.data)==null?void 0:S.active_project)==null?void 0:M.name)});const i=S=>S.catch(M=>console.debug("poll failed",M)),s=Ps(()=>i(go.poll()),1e3),o=Ps(()=>i(Oe.pollQueued()),1e3),r=Ps(()=>i(Oe.pollLast()),1e3),a=Ps(()=>i(eo.poll()),1e3),l={config:s,queued:o,last:r,logs:a};function c(S){for(const M of Object.values(l))M.stop();for(const M of ed(S))l[M].start()}function h(S){L(n,S,!0),c(S)}Ze(()=>{Yn.init(),c("overview")});var u=$x(),f=rt(u),d=C(f);td(d,{get active(){return b(n)},onnavigate:h,onshutdown:()=>pe.open({kind:"shutdown"})});var g=T(d,2),m=C(g);{var p=S=>{var M=Ux(),P=C(M);xg(P,{onaddlanguage:()=>pe.open({kind:"addLanguage"}),onremovelanguage:E=>pe.open({kind:"removeLanguage",language:E}),oneditconfig:()=>pe.open({kind:"editSerenaConfig"}),onopenmemory:E=>pe.open({kind:"editMemory",name:E}),oncreatememory:()=>pe.open({kind:"createMemory"}),ondeletememory:E=>pe.open({kind:"deleteMemory",name:E}),oncancelexecution:E=>E.is_running?pe.open({kind:"cancelExecution",execution:E}):void Oe.cancel(E)}),A(S,M)};tt(m,S=>{b(n)==="overview"&&S(p)})}var v=T(m,2);{var _=S=>{var M=Xx(),P=C(M);Og(P,{}),A(S,M)};tt(v,S=>{b(n)==="logs"&&S(_)})}var x=T(v,2);{var y=S=>{var M=qx(),P=C(M);vx(P,{}),A(S,M)};tt(x,S=>{b(n)==="stats"&&S(y)})}var w=T(f,2);Yx(w,{}),A(e,u),lt()}Af(Kx,{target:document.getElementById("app")}); diff --git a/src/serena/resources/dashboard/assets/index-DK0wQuCD.css b/src/serena/resources/dashboard/assets/index-DK0wQuCD.css deleted file mode 100644 index c05025a41..000000000 --- a/src/serena/resources/dashboard/assets/index-DK0wQuCD.css +++ /dev/null @@ -1 +0,0 @@ -:root{--bg: #f5f5f5;--bg-card: #ffffff;--bg-elevated: #ffffff;--bg-secondary-btn: #f0f2f5;--text-primary: #1f2328;--text-secondary: #3f4754;--text-muted: #6a737d;--border: #e3e6ea;--border-strong: #d0d7de;--accent: #eaa45d;--accent-hover: #dca662;--chart-2: #6aa3d8;--chart-3: #7fb77e;--chart-4: #d88c8c;--chart-5: #b39ddb;--chart-6: #e0a458;--text-on-accent: #ffffff;--btn-disabled: #adb5bd;--tool-highlight: #fff3bf;--tool-highlight-text: #1f2328;--btn-secondary-hover: #e3e6ea;--stats-header: #f0f2f5;--success: #22c55e;--log-debug: #8b95a1;--log-info: #1f2328;--log-warning: #d97706;--log-error: #dc2626;--radius: 6px;--radius-sm: 4px;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-6: 24px;--space-8: 32px;--max-width: 1600px;--font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-mono: "JetBrains Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;--shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 1px 3px rgba(15, 23, 42, .06);--shadow-elevated: 0 4px 12px rgba(15, 23, 42, .08);--focus-ring: 0 0 0 3px rgba(234, 164, 93, .18)}[data-theme=dark]{--bg: #1a1a1a;--bg-card: #2d2d2d;--bg-elevated: #262b32;--bg-secondary-btn: #2d333b;--text-primary: #e6edf3;--text-secondary: #c9d1d9;--text-muted: #8b95a1;--border: #2d333b;--border-strong: #3d444d;--btn-disabled: #4a5159;--tool-highlight: #f6c948;--tool-highlight-text: #15181c;--btn-secondary-hover: #3d444d;--stats-header: #262b32;--log-info: #e6edf3;--log-warning: #f59e0b;--log-error: #f87171;--shadow: 0 1px 2px rgba(0, 0, 0, .25), 0 1px 3px rgba(0, 0, 0, .35);--shadow-elevated: 0 4px 12px rgba(0, 0, 0, .45)}*{box-sizing:border-box}html{scrollbar-gutter:stable}body{margin:0;font-family:var(--font-sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background:var(--bg);color:var(--text-primary);transition:background-color .3s ease,color .3s ease}h1,h2,h3,h4,h5,h6{font-weight:600;letter-spacing:-.005em}code,pre,.mono{font-family:var(--font-mono)}a{color:inherit;text-decoration:none}input:focus-visible,textarea:focus-visible{outline:none;border-color:var(--accent);box-shadow:var(--focus-ring)}.modal-actions{display:flex;gap:var(--space-3);justify-content:flex-end;margin-top:var(--space-4)}.modal-info{color:var(--text-secondary)}.modal-hint{color:var(--text-muted);font-size:13px}.modal-input{width:100%;padding:var(--space-2);border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-card);color:var(--text-primary)}.modal-textarea{width:100%;font-family:var(--font-mono);font-size:13px;background:var(--bg-card);color:var(--text-primary);border:1px solid var(--border-strong);border-radius:var(--radius-sm);padding:var(--space-3)}.theme-toggle.svelte-ev54os{display:inline-flex;gap:var(--space-2);align-items:center;background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);padding:var(--space-2) var(--space-3);cursor:pointer;color:var(--text-primary)}.banner.svelte-1lo7lbo{position:relative;display:inline-flex;align-items:center}.banner.gold.svelte-1lo7lbo{display:flex;width:100%}.banner.gold.svelte-1lo7lbo a:where(.svelte-1lo7lbo){display:block;width:100%}.banner.svelte-1lo7lbo img:where(.svelte-1lo7lbo){max-height:90px;display:block}.banner.platinum.svelte-1lo7lbo img:where(.svelte-1lo7lbo){max-height:150px}.banner.gold.svelte-1lo7lbo img:where(.svelte-1lo7lbo){width:100%;height:auto;max-height:none;display:block}.header.svelte-xjepwq{display:flex;justify-content:space-between;align-items:center;gap:var(--space-6);padding:var(--space-6);background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);min-height:150px;max-width:var(--max-width);margin:0 auto}.header-left.svelte-xjepwq{display:flex;align-items:center;gap:var(--space-6)}.header-banner.svelte-xjepwq{display:flex;align-items:center}#serena-logo.svelte-xjepwq{height:130px}.header-nav.svelte-xjepwq{display:flex;flex-direction:column;align-items:flex-end;gap:var(--space-3)}.header-actions.svelte-xjepwq{position:relative;display:flex;gap:var(--space-2)}.shutdown-button.svelte-xjepwq{background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);padding:var(--space-2) var(--space-3);cursor:pointer;color:var(--text-primary);font-family:var(--font-sans)}.shutdown-button.svelte-xjepwq:hover{background:var(--bg-card)}.header-tabs.svelte-xjepwq{display:flex;gap:var(--space-4)}.header-tab.svelte-xjepwq{background:none;border:none;cursor:pointer;color:var(--text-primary);font-family:var(--font-sans);padding-bottom:var(--space-1)}.header-tab.active.svelte-xjepwq{color:var(--accent);border-bottom:2px solid var(--accent)}.collapsible-header.svelte-oy8gea{margin:0;font-size:inherit;font-weight:inherit}.collapsible-trigger.svelte-oy8gea{display:flex;justify-content:space-between;align-items:center;cursor:pointer;width:100%;background:none;border:none;padding:0;text-align:left}.collapsible-title.svelte-oy8gea{text-transform:uppercase;letter-spacing:.04em;font-size:15px;font-weight:600;color:var(--text-muted)}.collapsible-trigger.svelte-oy8gea:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.toggle-icon.svelte-oy8gea{font-size:10px;color:var(--text-muted);transition:transform .2s}.toggle-icon.open.svelte-oy8gea{transform:rotate(180deg)}.collapsible-content.svelte-oy8gea{margin-top:var(--space-3)}.btn.svelte-18f749u{font-family:var(--font-sans);font-weight:500;border:none;border-radius:var(--radius-sm);padding:var(--space-2) var(--space-4);cursor:pointer}.primary.svelte-18f749u{background:var(--accent);color:var(--text-on-accent)}.primary.svelte-18f749u:hover{background:var(--accent-hover)}.secondary.svelte-18f749u{background:var(--bg-secondary-btn);color:var(--text-primary)}.secondary.svelte-18f749u:hover{background:var(--btn-secondary-hover)}.danger.svelte-18f749u{background:var(--log-error);color:var(--text-on-accent)}.btn.svelte-18f749u:disabled{background:var(--btn-disabled);cursor:not-allowed}.config-grid.svelte-3p7ghk{display:grid;grid-template-columns:160px 1fr;gap:var(--space-2) var(--space-3);align-items:baseline;margin-bottom:var(--space-4)}.config-label.svelte-3p7ghk{text-transform:uppercase;letter-spacing:.03em;font-size:13px;font-weight:500;color:var(--text-muted)}.config-value.svelte-3p7ghk{color:var(--text-primary)}.config-display.svelte-3p7ghk .collapsible{margin-top:var(--space-4)}.languages-cell.svelte-3p7ghk{display:inline-flex;flex-wrap:wrap;gap:var(--space-2);align-items:center}.lang-badge.svelte-3p7ghk{background:var(--bg-secondary-btn);border-radius:var(--radius-sm);padding:2px var(--space-2);font-family:var(--font-mono);font-size:12px;display:inline-flex;align-items:center;gap:4px}.lang-remove.svelte-3p7ghk{border:none;background:none;cursor:pointer;color:var(--text-muted)}.tools-grid.svelte-3p7ghk{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:var(--space-2)}.tool-chip.svelte-3p7ghk{background:var(--bg);padding:6px 10px;border-radius:3px;font-family:var(--font-mono);font-size:13px;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.memories-container.svelte-3p7ghk{display:flex;flex-wrap:wrap;gap:var(--space-2)}.memory-item.svelte-3p7ghk{position:relative;display:inline-flex;align-items:center;padding:8px 30px 8px 12px;border-radius:4px;background:var(--bg);border:1px solid var(--border)}.memory-name.svelte-3p7ghk{background:none;border:none;cursor:pointer;font-family:var(--font-mono);font-size:13px;color:var(--text-primary);padding:0}.memory-remove.svelte-3p7ghk{position:absolute;top:2px;right:2px;width:18px;height:18px;display:flex;align-items:center;justify-content:center;background:none;border:none;border-radius:3px;cursor:pointer;color:var(--log-error);font-size:14px;font-weight:700;line-height:1}.memory-remove.svelte-3p7ghk:hover{background:var(--bg-secondary-btn)}.memory-add-btn.svelte-3p7ghk{display:inline-flex;align-items:center;padding:8px 12px;border-radius:4px;border:1px dashed var(--border-strong);background:var(--bg-card);color:var(--text-primary);cursor:pointer;font-family:var(--font-sans);font-size:13px}.memory-add-btn.svelte-3p7ghk:hover{border-color:var(--accent)}.config-footer.svelte-3p7ghk{display:flex;justify-content:space-between;align-items:center;gap:var(--space-3);margin-top:var(--space-4);flex-wrap:wrap}.config-guide-link.svelte-3p7ghk{color:var(--accent);font-weight:500;text-decoration:none}.config-guide-link.svelte-3p7ghk:hover{text-decoration:underline}.bar-row.svelte-vf5ryt{display:grid;grid-template-columns:1fr 3fr auto;gap:var(--space-2);align-items:center;margin:var(--space-1) 0}.bar-name.svelte-vf5ryt{font-family:var(--font-mono);font-size:12px}.bar-track.svelte-vf5ryt{background:var(--bg-secondary-btn);border-radius:var(--radius-sm);height:14px}.bar-fill.svelte-vf5ryt{background:var(--accent);height:100%;border-radius:var(--radius-sm)}.bar-count.svelte-vf5ryt{font-size:12px;color:var(--text-muted)}.no-stats-message.svelte-vf5ryt{color:var(--text-muted)}.card.svelte-xtxwfc{background:var(--bg-card);border:1px solid var(--border);padding:var(--space-6);border-radius:var(--radius);box-shadow:var(--shadow);margin-bottom:var(--space-4)}.card-title.svelte-xtxwfc{text-transform:uppercase;letter-spacing:.04em;font-size:15px;font-weight:600;color:var(--text-muted);margin:0 0 var(--space-4) 0}.list-panel.svelte-1mlfgti{list-style:none;margin:0;padding:0;max-height:340px;overflow-y:auto;display:flex;flex-direction:column;gap:var(--space-1)}.list-panel.svelte-1mlfgti li:where(.svelte-1mlfgti){padding:var(--space-2);border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-elevated);font-family:var(--font-mono);font-size:12px;color:var(--text-primary)}.list-panel.svelte-1mlfgti li.active:where(.svelte-1mlfgti){background:var(--accent);color:var(--text-on-accent);border-color:var(--accent)}.no-stats-message.svelte-1mlfgti{color:var(--text-muted)}.projects.svelte-19jixoo{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--space-2)}.project.svelte-19jixoo{padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);background:var(--bg);border:1px solid var(--border)}.project.active.svelte-19jixoo{background:var(--accent);color:var(--text-on-accent);border-color:var(--accent)}.project-name.svelte-19jixoo{font-weight:700;font-size:13px;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.project-path.svelte-19jixoo{font-family:var(--font-mono);font-size:11px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.project.active.svelte-19jixoo .project-path:where(.svelte-19jixoo){color:var(--text-on-accent);opacity:.85}.no-stats-message.svelte-19jixoo{color:var(--text-muted)}.spinner.svelte-1wjj49t{width:16px;height:16px;border:2px solid var(--border-strong);border-top-color:var(--accent);border-radius:50%;animation:svelte-1wjj49t-spin .7s linear infinite;display:inline-block}@keyframes svelte-1wjj49t-spin{to{transform:rotate(360deg)}}.execution-item.svelte-1wrcimm{display:inline-flex;align-items:center;gap:var(--space-2);border:1px solid var(--border);border-radius:999px;padding:var(--space-1) var(--space-3);margin:2px}.execution-item.running.svelte-1wrcimm{border-color:var(--accent)}.execution-name.svelte-1wrcimm{font-family:var(--font-mono);font-size:12px}.cancel-btn.svelte-1wrcimm{border:none;background:none;cursor:pointer;color:var(--text-muted)}.cancel-error.svelte-1wrcimm{color:var(--log-error);margin:var(--space-2) 0 0}.no-stats-message.svelte-1wrcimm{color:var(--text-muted)}.last-exec.svelte-1772y66{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);border:1px solid var(--border);border-radius:var(--radius)}.last-exec.ok.svelte-1772y66{border-color:var(--success);background:color-mix(in srgb,var(--success) 8%,transparent)}.last-exec.fail.svelte-1772y66{border-color:var(--log-error);background:color-mix(in srgb,var(--log-error) 8%,transparent)}.icon.svelte-1772y66{display:inline-flex;width:22px;height:22px;align-items:center;justify-content:center;border-radius:50%;font-size:12px}.ok.svelte-1772y66 .icon:where(.svelte-1772y66){background:color-mix(in srgb,var(--success) 20%,transparent);color:var(--success)}.fail.svelte-1772y66 .icon:where(.svelte-1772y66){background:color-mix(in srgb,var(--log-error) 20%,transparent);color:var(--log-error)}.body.svelte-1772y66{display:flex;flex-direction:column}.status.svelte-1772y66{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted)}.execution-name.svelte-1772y66{font-family:var(--font-mono);font-size:13px;color:var(--text-primary)}.meta.svelte-1772y66{margin-left:auto;font-family:var(--font-mono);font-size:12px;color:var(--text-muted)}.no-stats-message.svelte-1772y66{color:var(--text-muted)}.cancelled-item.svelte-1re8sys{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-1) 0;font-family:var(--font-mono);font-size:12px;color:var(--text-secondary)}.icon.svelte-1re8sys{display:inline-flex;width:16px;height:16px;align-items:center;justify-content:center;border-radius:50%;background:var(--bg-secondary-btn);color:var(--log-error);font-size:10px}.cancelled-item.abandoned.svelte-1re8sys .icon:where(.svelte-1re8sys){color:var(--log-warning)}.meta.svelte-1re8sys{margin-left:auto;color:var(--text-muted)}.news-item.svelte-1yvzfte{border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-3);margin-bottom:var(--space-3);background:var(--bg-card);box-shadow:var(--shadow)}.news-dismiss.svelte-1yvzfte{margin-top:var(--space-2);background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);padding:var(--space-1) var(--space-3);cursor:pointer;color:var(--text-primary)}.overview-container.svelte-1im2p0o{display:grid;grid-template-columns:minmax(0,1fr) 400px;gap:var(--space-6)}@media(max-width:1000px){.overview-container.svelte-1im2p0o{grid-template-columns:1fr}}.log-action-buttons.svelte-pdgm8l{display:flex;gap:var(--space-2);justify-content:flex-end;margin-bottom:var(--space-2)}.log-action-btn.svelte-pdgm8l{background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);padding:var(--space-1) var(--space-3);cursor:pointer;color:var(--text-primary)}.log-action-btn.svelte-pdgm8l:disabled{color:var(--btn-disabled);cursor:not-allowed}.danger.svelte-pdgm8l:not(:disabled){color:var(--log-error)}.log-container.svelte-1f7p4v8{height:calc(100vh - 220px);overflow:auto;font-family:var(--font-mono);font-size:12px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-3);box-shadow:var(--shadow)}.log-line.svelte-1f7p4v8{white-space:pre-wrap}.debug.svelte-1f7p4v8{color:var(--log-debug)}.info.svelte-1f7p4v8{color:var(--log-info)}.warning.svelte-1f7p4v8{color:var(--log-warning)}.error.svelte-1f7p4v8{color:var(--log-error)}.log-line .tool-name{background-color:var(--tool-highlight);color:var(--tool-highlight-text);font-weight:700}.chart-group.svelte-1tmlu4m{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-4);box-shadow:var(--shadow)}table.svelte-ebpvur{width:100%;border-collapse:collapse}td.svelte-ebpvur{padding:var(--space-2);border-bottom:1px solid var(--border);font-family:var(--font-mono)}.controls.svelte-af288w{display:flex;gap:var(--space-3);margin-bottom:var(--space-4)}.charts-grid.svelte-af288w{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-4);margin-top:var(--space-4)}.charts-bars.svelte-af288w{display:grid;grid-template-columns:1fr;gap:var(--space-4);margin-top:var(--space-4)}@media(max-width:1000px){.charts-grid.svelte-af288w{grid-template-columns:1fr}}.estimator-name.svelte-af288w{color:var(--text-muted);margin:var(--space-2) 0}.no-stats-message.svelte-af288w{color:var(--text-muted)}.backdrop.svelte-19jhfg1{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000073;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:1000}.modal-content.svelte-19jhfg1{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-6);max-width:600px;width:90%;box-shadow:var(--shadow-elevated);position:relative}.modal-error.svelte-19jhfg1{color:var(--log-error);margin:0 0 var(--space-3)}.modal-close.svelte-19jhfg1{position:absolute;top:var(--space-3);right:var(--space-4);cursor:pointer;font-size:22px;color:var(--text-muted);background:none;border:none;line-height:1;padding:0}.combobox.svelte-hol9vh{position:relative}input.svelte-hol9vh{width:100%;padding:var(--space-2);border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-card);color:var(--text-primary);font-family:var(--font-sans)}.combobox-caret.svelte-hol9vh{position:absolute;right:var(--space-2);top:var(--space-2);pointer-events:none}.combobox-options.svelte-hol9vh{list-style:none;margin:0;padding:0;position:absolute;z-index:5;width:100%;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-sm);max-height:200px;overflow:auto}.combobox-options.svelte-hol9vh li:where(.svelte-hol9vh){padding:var(--space-2);cursor:pointer}.combobox-options.svelte-hol9vh li:where(.svelte-hol9vh):hover{background:var(--bg-secondary-btn)}.combobox-empty.svelte-hol9vh{padding:var(--space-2);color:var(--text-muted)}.memory-rename-input.svelte-blcc47{font-family:var(--font-mono);font-size:14px;padding:var(--space-1) var(--space-2);border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-card);color:var(--text-primary)}.memory-name-display.svelte-blcc47{font-family:var(--font-mono)}.rename-trigger.svelte-blcc47{background:none;border:none;cursor:pointer;color:var(--text-muted)}.main.svelte-1n46o8q{max-width:var(--max-width);margin:0 auto;padding:var(--space-6)} diff --git a/src/serena/resources/dashboard/assets/index-Jw8r4LTr.js b/src/serena/resources/dashboard/assets/index-Jw8r4LTr.js deleted file mode 100644 index 6c09ab305..000000000 --- a/src/serena/resources/dashboard/assets/index-Jw8r4LTr.js +++ /dev/null @@ -1,26 +0,0 @@ -var Hs=Object.defineProperty;var Ba=e=>{throw TypeError(e)};var zs=(e,t,n)=>t in e?Hs(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var _t=(e,t,n)=>zs(e,typeof t!="symbol"?t+"":t,n),Br=(e,t,n)=>t.has(e)||Ba("Cannot "+n);var g=(e,t,n)=>(Br(e,t,"read from private field"),n?n.call(e):t.get(e)),R=(e,t,n)=>t.has(e)?Ba("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n),D=(e,t,n,r)=>(Br(e,t,"write to private field"),r?r.call(e,n):t.set(e,n),n),U=(e,t,n)=>(Br(e,t,"access private method"),n);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))r(a);new MutationObserver(a=>{for(const i of a)if(i.type==="childList")for(const s of i.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&r(s)}).observe(document,{childList:!0,subtree:!0});function n(a){const i={};return a.integrity&&(i.integrity=a.integrity),a.referrerPolicy&&(i.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?i.credentials="include":a.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(a){if(a.ep)return;a.ep=!0;const i=n(a);fetch(a.href,i)}})();const wi=!1;var xi=Array.isArray,Ws=Array.prototype.indexOf,Ve=Array.prototype.includes,Rr=Array.from,Bs=Object.defineProperty,Hn=Object.getOwnPropertyDescriptor,ki=Object.getOwnPropertyDescriptors,Us=Object.prototype,Vs=Array.prototype,Ea=Object.getPrototypeOf,Ua=Object.isExtensible;const qs=()=>{};function Ys(e){return e()}function ra(e){for(var t=0;t{e=r,t=a});return{promise:n,resolve:e,reject:t}}function Ei(e,t){if(Array.isArray(e))return e;if(!(Symbol.iterator in e))return Array.from(e);const n=[];for(const r of e)if(n.push(r),n.length===t)break;return n}const ht=2,yn=4,tr=8,Ti=1<<24,Jt=16,$t=32,Se=64,aa=128,zt=512,ot=1024,ut=2048,oe=4096,yt=8192,Qt=16384,$e=32768,ia=1<<25,Xe=65536,kr=1<<17,Gs=1<<18,En=1<<19,Ci=1<<20,se=1<<25,Je=65536,Ar=1<<21,ln=1<<22,Ee=1<<23,un=Symbol("$state"),Ks=Symbol(""),hr=Symbol("attributes"),sa=Symbol("class"),oa=Symbol("style"),Dn=Symbol("text"),pr=Symbol("form reset"),Ir=new class extends Error{constructor(){super(...arguments);_t(this,"name","StaleReactionError");_t(this,"message","The reaction that called `getAbortSignal()` was re-run or destroyed")}};function Si(e){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function Xs(){throw new Error("https://svelte.dev/e/async_derived_orphan")}function Js(e,t,n){throw new Error("https://svelte.dev/e/each_key_duplicate")}function Zs(e){throw new Error("https://svelte.dev/e/effect_in_teardown")}function Qs(){throw new Error("https://svelte.dev/e/effect_in_unowned_derived")}function $s(e){throw new Error("https://svelte.dev/e/effect_orphan")}function to(){throw new Error("https://svelte.dev/e/effect_update_depth_exceeded")}function eo(){throw new Error("https://svelte.dev/e/state_descriptors_fixed")}function no(){throw new Error("https://svelte.dev/e/state_prototype_fixed")}function ro(){throw new Error("https://svelte.dev/e/state_unsafe_mutation")}function ao(){throw new Error("https://svelte.dev/e/svelte_boundary_reset_onerror")}const io=1,so=2,Pi=4,oo=8,lo=16,uo=1,co=2,st=Symbol("uninitialized"),Oi="http://www.w3.org/1999/xhtml",fo="http://www.w3.org/2000/svg",vo="http://www.w3.org/1998/Math/MathML";function ho(){console.warn("https://svelte.dev/e/derived_inert")}function po(){console.warn("https://svelte.dev/e/svelte_boundary_reset_noop")}function Li(e){return e===this.v}function go(e,t){return e!=e?t==t:e!==t||e!==null&&typeof e=="object"||typeof e=="function"}function Mi(e){return!go(e,this.v)}let er=!1,mo=!1;function yo(){er=!0}let tt=null;function _n(e){tt=e}function K(e,t=!1,n){tt={p:tt,i:!1,c:null,e:null,s:e,x:null,r:z,l:er&&!t?{s:null,u:null,$:[]}:null}}function X(e){var t=tt,n=t.e;if(n!==null){t.e=null;for(var r of n)es(r)}return t.i=!0,tt=t.p,{}}function Tn(){return!er||tt!==null&&tt.l===null}let je=[];function Ni(){var e=je;je=[],ra(e)}function Te(e){if(je.length===0&&!Wn){var t=je;queueMicrotask(()=>{t===je&&Ni()})}je.push(e)}function _o(){for(;je.length>0;)Ni()}function Di(e){var t=z;if(t===null)return H.f|=Ee,e;if((t.f&$e)===0&&(t.f&yn)===0)throw e;ke(e,t)}function ke(e,t){for(;t!==null;){if((t.f&aa)!==0){if((t.f&$e)===0)throw e;try{t.b.error(e);return}catch(n){e=n}}t=t.parent}throw e}const bo=-7169;function nt(e,t){e.f=e.f&bo|t}function Ta(e){(e.f&zt)!==0||e.deps===null?nt(e,ot):nt(e,oe)}function ji(e){if(e!==null)for(const t of e)(t.f&ht)===0||(t.f&Je)===0||(t.f^=Je,ji(t.deps))}function Ri(e,t,n){(e.f&ut)!==0?t.add(e):(e.f&oe)!==0&&n.add(e),ji(e.deps),nt(e,ot)}let Ur=null,tn=null,L=null,zn=null,dt=null,la=null,Wn=!1,Vr=!1,sn=null,gr=null;var Va=0;let wo=1;var dn,be,Fe,vn,hn,He,pn,ce,Kn,St,Xn,we,ne,re,gn,ze,q,ua,jn,ca,Ii,Fi,mr,xo,fa,nn;const Nr=class Nr{constructor(){R(this,q);_t(this,"id",wo++);R(this,dn,!1);_t(this,"linked",!0);R(this,be,null);R(this,Fe,null);_t(this,"async_deriveds",new Map);_t(this,"current",new Map);_t(this,"previous",new Map);_t(this,"unblocked",new Set);R(this,vn,new Set);R(this,hn,new Set);R(this,He,new Set);R(this,pn,0);R(this,ce,new Map);R(this,Kn,null);R(this,St,[]);R(this,Xn,[]);R(this,we,new Set);R(this,ne,new Set);R(this,re,new Map);R(this,gn,new Set);_t(this,"is_fork",!1);R(this,ze,!1)}skip_effect(t){g(this,re).has(t)||g(this,re).set(t,{d:[],m:[]}),g(this,gn).delete(t)}unskip_effect(t,n=r=>this.schedule(r)){var r=g(this,re).get(t);if(r){g(this,re).delete(t);for(var a of r.d)nt(a,ut),n(a);for(a of r.m)nt(a,oe),n(a)}g(this,gn).add(t)}capture(t,n,r=!1){t.v!==st&&!this.previous.has(t)&&this.previous.set(t,t.v),(t.f&Ee)===0&&(this.current.set(t,[n,r]),dt==null||dt.set(t,n)),this.is_fork||(t.v=n)}activate(){L=this}deactivate(){L=null,dt=null}flush(){try{Vr=!0,L=this,U(this,q,jn).call(this)}finally{Va=0,la=null,sn=null,gr=null,Vr=!1,L=null,dt=null,qe.clear()}}discard(){for(const t of g(this,hn))t(this);g(this,hn).clear(),g(this,He).clear(),U(this,q,nn).call(this)}register_created_effect(t){g(this,Xn).push(t)}increment(t,n){if(D(this,pn,g(this,pn)+1),t){let r=g(this,ce).get(n)??0;g(this,ce).set(n,r+1)}}decrement(t,n){if(D(this,pn,g(this,pn)-1),t){let r=g(this,ce).get(n)??0;r===1?g(this,ce).delete(n):g(this,ce).set(n,r-1)}g(this,ze)||(D(this,ze,!0),Te(()=>{D(this,ze,!1),this.linked&&this.flush()}))}transfer_effects(t,n){for(const r of t)g(this,we).add(r);for(const r of n)g(this,ne).add(r);t.clear(),n.clear()}oncommit(t){g(this,vn).add(t)}ondiscard(t){g(this,hn).add(t)}on_fork_commit(t){g(this,He).add(t)}run_fork_commit_callbacks(){for(const t of g(this,He))t(this);g(this,He).clear()}settled(){return(g(this,Kn)??D(this,Kn,Ai())).promise}static ensure(){var t;if(L===null){const n=L=new Nr;U(t=n,q,fa).call(t),!Vr&&!Wn&&Te(()=>{g(n,dn)||n.flush()})}return L}apply(){{dt=null;return}}schedule(t){var a;if(la=t,(a=t.b)!=null&&a.is_pending&&(t.f&(yn|tr|Ti))!==0&&(t.f&$e)===0){t.b.defer_effect(t);return}for(var n=t;n.parent!==null;){n=n.parent;var r=n.f;if(sn!==null&&n===z&&(H===null||(H.f&ht)===0))return;if((r&(Se|$t))!==0){if((r&ot)===0)return;n.f^=ot}}g(this,St).push(n)}};dn=new WeakMap,be=new WeakMap,Fe=new WeakMap,vn=new WeakMap,hn=new WeakMap,He=new WeakMap,pn=new WeakMap,ce=new WeakMap,Kn=new WeakMap,St=new WeakMap,Xn=new WeakMap,we=new WeakMap,ne=new WeakMap,re=new WeakMap,gn=new WeakMap,ze=new WeakMap,q=new WeakSet,ua=function(){if(this.is_fork)return!0;for(const r of g(this,ce).keys()){for(var t=r,n=!1;t.parent!==null;){if(g(this,re).has(t)){n=!0;break}t=t.parent}if(!n)return!0}return!1},jn=function(){var l,u,c,f;if(D(this,dn,!0),Va++>1e3&&(U(this,q,nn).call(this),Ao()),!U(this,q,ua).call(this)){for(const d of g(this,we))g(this,ne).delete(d),nt(d,ut),this.schedule(d);for(const d of g(this,ne))nt(d,oe),this.schedule(d)}const t=g(this,St);D(this,St,[]),this.apply();var n=sn=[],r=[],a=gr=[];for(const d of t)try{U(this,q,ca).call(this,d,n,r)}catch(h){throw Wi(d),h}if(L=null,a.length>0){var i=Nr.ensure();for(const d of a)i.schedule(d)}if(sn=null,gr=null,U(this,q,ua).call(this)){U(this,q,mr).call(this,r),U(this,q,mr).call(this,n);for(const[d,h]of g(this,re))zi(d,h);a.length>0&&U(l=L,q,jn).call(l);return}const s=U(this,q,Ii).call(this);if(s){U(u=s,q,Fi).call(u,this);return}g(this,we).clear(),g(this,ne).clear();for(const d of g(this,vn))d(this);g(this,vn).clear(),zn=this,qa(r),qa(n),zn=null,(c=g(this,Kn))==null||c.resolve();var o=L;if(this.linked&&g(this,pn)===0&&U(this,q,nn).call(this),g(this,St).length>0){o===null&&(o=this,U(this,q,fa).call(this));const d=o;g(d,St).push(...g(this,St).filter(h=>!g(d,St).includes(h)))}o!==null&&U(f=o,q,jn).call(f)},ca=function(t,n,r){t.f^=ot;for(var a=t.first;a!==null;){var i=a.f,s=(i&($t|Se))!==0,o=s&&(i&ot)!==0,l=o||(i&yt)!==0||g(this,re).has(a);if(!l&&a.fn!==null){s?a.f^=ot:(i&yn)!==0?n.push(a):ar(a)&&((i&Jt)!==0&&g(this,ne).add(a),wn(a));var u=a.first;if(u!==null){a=u;continue}}for(;a!==null;){var c=a.next;if(c!==null){a=c;break}a=a.parent}}},Ii=function(){for(var t=g(this,be);t!==null;){if(!t.is_fork){for(const[n,[,r]]of this.current)if(t.current.has(n)&&!r)return t}t=g(t,be)}return null},Fi=function(t){var r;for(const[a,i]of t.current)!this.previous.has(a)&&t.previous.has(a)&&this.previous.set(a,t.previous.get(a)),this.current.set(a,i);for(const[a,i]of t.async_deriveds){const s=this.async_deriveds.get(a);s&&i.promise.then(s.resolve)}const n=a=>{var i=a.reactions;if(i!==null)for(const l of i){var s=l.f;if((s&ht)!==0)n(l);else{var o=l;s&(ln|Jt)&&!this.async_deriveds.has(o)&&(g(this,ne).delete(o),nt(o,ut),this.schedule(o))}}};for(const a of this.current.keys())n(a);this.oncommit(()=>t.discard()),U(r=t,q,nn).call(r),L=this,U(this,q,jn).call(this)},mr=function(t){for(var n=0;n!this.current.has(d));if(a.length===0)t&&f.discard();else if(n.length>0){if(t)for(const d of g(this,gn))f.unskip_effect(d,h=>{var p;(h.f&(Jt|ln))!==0?f.schedule(h):U(p=f,q,mr).call(p,[h])});f.activate();var i=new Set,s=new Map;for(var o of n)Hi(o,a,i,s);s=new Map;var l=[...f.current.keys()].filter(d=>this.current.has(d)?this.current.get(d)[0]!==d.v:!0);if(l.length>0)for(const d of g(this,Xn))(d.f&(Qt|yt|kr))===0&&Ca(d,l,s)&&((d.f&(ln|Jt))!==0?(nt(d,ut),f.schedule(d)):g(f,we).add(d));if(g(f,St).length>0&&!g(f,ze)){f.apply();for(var u of g(f,St))U(c=f,q,ca).call(c,u,[],[]);D(f,St,[])}f.deactivate()}}}},fa=function(){tn===null?Ur=tn=this:(D(tn,Fe,this),D(this,be,tn)),tn=this},nn=function(){var t=g(this,be),n=g(this,Fe);t===null?Ur=n:D(t,Fe,n),n===null?tn=t:D(n,be,t),this.linked=!1};let Ze=Nr;function ko(e){var t=Wn;Wn=!0;try{for(var n;;){if(_o(),L===null)return n;L.flush()}}finally{Wn=t}}function Ao(){try{to()}catch(e){ke(e,la)}}let Gt=null;function qa(e){var t=e.length;if(t!==0){for(var n=0;n0)){qe.clear();for(const a of Gt){if((a.f&(Qt|yt))!==0)continue;const i=[a];let s=a.parent;for(;s!==null;)Gt.has(s)&&(Gt.delete(s),i.push(s)),s=s.parent;for(let o=i.length-1;o>=0;o--){const l=i[o];(l.f&(Qt|yt))===0&&wn(l)}}Gt.clear()}}Gt=null}}function Hi(e,t,n,r){if(!n.has(e)&&(n.add(e),e.reactions!==null))for(const a of e.reactions){const i=a.f;(i&ht)!==0?Hi(a,t,n,r):(i&(ln|Jt))!==0&&(i&ut)===0&&Ca(a,t,r)&&(nt(a,ut),Sa(a))}}function Ca(e,t,n){const r=n.get(e);if(r!==void 0)return r;if(e.deps!==null)for(const a of e.deps){if(Ve.call(t,a))return!0;if((a.f&ht)!==0&&Ca(a,t,n))return n.set(a,!0),!0}return n.set(e,!1),!1}function Sa(e){L.schedule(e)}function zi(e,t){if(!((e.f&$t)!==0&&(e.f&ot)!==0)){(e.f&ut)!==0?t.d.push(e):(e.f&oe)!==0&&t.m.push(e),nt(e,ot);for(var n=e.first;n!==null;)zi(n,t),n=n.next}}function Wi(e){nt(e,ot);for(var t=e.first;t!==null;)Wi(t),t=t.next}function Eo(e){let t=0,n=Qe(0),r;return()=>{Oa()&&(v(n),La(()=>(t===0&&(r=he(()=>e(()=>Bn(n)))),t+=1,()=>{Te(()=>{t-=1,t===0&&(r==null||r(),r=void 0,Bn(n))})})))}}var To=Xe|En;function Co(e,t,n,r){new So(e,t,n,r)}var jt,Aa,Rt,We,bt,It,mt,Pt,fe,Be,xe,mn,Jn,Zn,de,Dr,rt,Po,Oo,Lo,da,yr,_r,va,ha;class So{constructor(t,n,r,a){R(this,rt);_t(this,"parent");_t(this,"is_pending",!1);_t(this,"transform_error");R(this,jt);R(this,Aa,null);R(this,Rt);R(this,We);R(this,bt);R(this,It,null);R(this,mt,null);R(this,Pt,null);R(this,fe,null);R(this,Be,0);R(this,xe,0);R(this,mn,!1);R(this,Jn,new Set);R(this,Zn,new Set);R(this,de,null);R(this,Dr,Eo(()=>(D(this,de,Qe(g(this,Be))),()=>{D(this,de,null)})));var i;D(this,jt,t),D(this,Rt,n),D(this,We,s=>{var o=z;o.b=this,o.f|=aa,r(s)}),this.parent=z.b,this.transform_error=a??((i=this.parent)==null?void 0:i.transform_error)??(s=>s),D(this,bt,rr(()=>{U(this,rt,da).call(this)},To))}defer_effect(t){Ri(t,g(this,Jn),g(this,Zn))}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!g(this,Rt).pending}update_pending_count(t,n){U(this,rt,va).call(this,t,n),D(this,Be,g(this,Be)+t),!(!g(this,de)||g(this,mn))&&(D(this,mn,!0),Te(()=>{D(this,mn,!1),g(this,de)&&bn(g(this,de),g(this,Be))}))}get_effect_pending(){return g(this,Dr).call(this),v(g(this,de))}error(t){if(!g(this,Rt).onerror&&!g(this,Rt).failed)throw t;L!=null&&L.is_fork?(g(this,It)&&L.skip_effect(g(this,It)),g(this,mt)&&L.skip_effect(g(this,mt)),g(this,Pt)&&L.skip_effect(g(this,Pt)),L.on_fork_commit(()=>{U(this,rt,ha).call(this,t)})):U(this,rt,ha).call(this,t)}}jt=new WeakMap,Aa=new WeakMap,Rt=new WeakMap,We=new WeakMap,bt=new WeakMap,It=new WeakMap,mt=new WeakMap,Pt=new WeakMap,fe=new WeakMap,Be=new WeakMap,xe=new WeakMap,mn=new WeakMap,Jn=new WeakMap,Zn=new WeakMap,de=new WeakMap,Dr=new WeakMap,rt=new WeakSet,Po=function(){try{D(this,It,Ft(()=>g(this,We).call(this,g(this,jt))))}catch(t){this.error(t)}},Oo=function(t){const n=g(this,Rt).failed;n&&D(this,Pt,Ft(()=>{n(g(this,jt),()=>t,()=>()=>{})}))},Lo=function(){const t=g(this,Rt).pending;t&&(this.is_pending=!0,D(this,mt,Ft(()=>t(g(this,jt)))),Te(()=>{var n=D(this,fe,document.createDocumentFragment()),r=ve();n.append(r),D(this,It,U(this,rt,_r).call(this,()=>Ft(()=>g(this,We).call(this,r)))),g(this,xe)===0&&(g(this,jt).before(n),D(this,fe,null),Ye(g(this,mt),()=>{D(this,mt,null)}),U(this,rt,yr).call(this,L))}))},da=function(){try{if(this.is_pending=this.has_pending_snippet(),D(this,xe,0),D(this,Be,0),D(this,It,Ft(()=>{g(this,We).call(this,g(this,jt))})),g(this,xe)>0){var t=D(this,fe,document.createDocumentFragment());Da(g(this,It),t);const n=g(this,Rt).pending;D(this,mt,Ft(()=>n(g(this,jt))))}else U(this,rt,yr).call(this,L)}catch(n){this.error(n)}},yr=function(t){this.is_pending=!1,t.transfer_effects(g(this,Jn),g(this,Zn))},_r=function(t){var n=z,r=H,a=tt;le(g(this,bt)),Ut(g(this,bt)),_n(g(this,bt).ctx);try{return Ze.ensure(),t()}catch(i){return Di(i),null}finally{le(n),Ut(r),_n(a)}},va=function(t,n){var r;if(!this.has_pending_snippet()){this.parent&&U(r=this.parent,rt,va).call(r,t,n);return}D(this,xe,g(this,xe)+t),g(this,xe)===0&&(U(this,rt,yr).call(this,n),g(this,mt)&&Ye(g(this,mt),()=>{D(this,mt,null)}),g(this,fe)&&(g(this,jt).before(g(this,fe)),D(this,fe,null)))},ha=function(t){g(this,It)&&(xt(g(this,It)),D(this,It,null)),g(this,mt)&&(xt(g(this,mt)),D(this,mt,null)),g(this,Pt)&&(xt(g(this,Pt)),D(this,Pt,null));var n=g(this,Rt).onerror;let r=g(this,Rt).failed;var a=!1,i=!1;const s=()=>{if(a){po();return}a=!0,i&&ao(),g(this,Pt)!==null&&Ye(g(this,Pt),()=>{D(this,Pt,null)}),U(this,rt,_r).call(this,()=>{U(this,rt,da).call(this)})},o=l=>{try{i=!0,n==null||n(l,s),i=!1}catch(u){ke(u,g(this,bt)&&g(this,bt).parent)}r&&D(this,Pt,U(this,rt,_r).call(this,()=>{try{return Ft(()=>{var u=z;u.b=this,u.f|=aa,r(g(this,jt),()=>l,()=>s)})}catch(u){return ke(u,g(this,bt).parent),null}}))};Te(()=>{var l;try{l=this.transform_error(t)}catch(u){ke(u,g(this,bt)&&g(this,bt).parent);return}l!==null&&typeof l=="object"&&typeof l.then=="function"?l.then(o,u=>ke(u,g(this,bt)&&g(this,bt).parent)):o(l)})};function Mo(e,t,n,r){const a=Tn()?Fr:Ui;var i=e.filter(d=>!d.settled);if(n.length===0&&i.length===0){r(t.map(a));return}var s=z,o=No(),l=i.length===1?i[0].promise:i.length>1?Promise.all(i.map(d=>d.promise)):null;function u(d){if((s.f&Qt)===0){o();try{r(d)}catch(h){ke(h,s)}Er()}}var c=Bi();if(n.length===0){l.then(()=>u(t.map(a))).finally(c);return}function f(){Promise.all(n.map(d=>Do(d))).then(d=>u([...t.map(a),...d])).catch(d=>ke(d,s)).finally(c)}l?l.then(()=>{o(),f(),Er()}):f()}function No(){var e=z,t=H,n=tt,r=L;return function(i=!0){le(e),Ut(t),_n(n),i&&(e.f&Qt)===0&&(r==null||r.activate(),r==null||r.apply())}}function Er(e=!0){le(null),Ut(null),_n(null),e&&(L==null||L.deactivate())}function Bi(){var e=z,t=e.b,n=L,r=t.is_rendered();return t.update_pending_count(1,n),n.increment(r,e),()=>{t.update_pending_count(-1,n),n.decrement(r,e)}}function Fr(e){var t=ht|ut;return z!==null&&(z.f|=En),{ctx:tt,deps:null,effects:null,equals:Li,f:t,fn:e,reactions:null,rv:0,v:st,wv:0,parent:z,ac:null}}const cr=Symbol("obsolete");function Do(e,t,n){let r=z;r===null&&Xs();var a=void 0,i=Qe(st),s=!H,o=new Set;return Go(()=>{var h;var l=z,u=Ai();a=u.promise;try{Promise.resolve(e()).then(u.resolve,p=>{p!==Ir&&u.reject(p)}).finally(Er)}catch(p){u.reject(p),Er()}var c=L;if(s){if((l.f&$e)!==0)var f=Bi();if(r.b.is_rendered())(h=c.async_deriveds.get(l))==null||h.reject(cr);else for(const p of o.values())p.reject(cr);o.add(u),c.async_deriveds.set(l,u)}const d=(p,y=void 0)=>{f==null||f(),o.delete(u),y!==cr&&(c.activate(),y?(i.f|=Ee,bn(i,y)):((i.f&Ee)!==0&&(i.f^=Ee),bn(i,p)),c.deactivate())};u.promise.then(d,p=>d(null,p||"unknown"))}),ts(()=>{for(const l of o)l.reject(cr)}),new Promise(l=>{function u(c){function f(){c===a?l(i):u(a)}c.then(f,f)}u(a)})}function J(e){const t=Fr(e);return os(t),t}function Ui(e){const t=Fr(e);return t.equals=Mi,t}function jo(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n0&&!Yi&&Fo()}return t}function Fo(){Yi=!1;for(const e of Tr){(e.f&ot)!==0&&nt(e,oe);let t;try{t=ar(e)}catch{t=!0}t&&wn(e)}Tr.clear()}function Bn(e){S(e,e.v+1)}function Gi(e,t,n){var r=e.reactions;if(r!==null)for(var a=Tn(),i=r.length,s=0;s{if(Ge===i)return o();var l=H,u=Ge;Ut(null),Ka(i);var c=o();return Ut(l),Ka(u),c};return r&&n.set("length",N(e.length)),new Proxy(e,{defineProperty(o,l,u){(!("value"in u)||u.configurable===!1||u.enumerable===!1||u.writable===!1)&&eo();var c=n.get(l);return c===void 0?s(()=>{var f=N(u.value);return n.set(l,f),f}):S(c,u.value,!0),!0},deleteProperty(o,l){var u=n.get(l);if(u===void 0){if(l in o){const c=s(()=>N(st));n.set(l,c),Bn(a)}}else S(u,st),Bn(a);return!0},get(o,l,u){var h;if(l===un)return e;var c=n.get(l),f=l in o;if(c===void 0&&(!f||(h=Hn(o,l))!=null&&h.writable)&&(c=s(()=>{var p=vt(f?o[l]:st),y=N(p);return y}),n.set(l,c)),c!==void 0){var d=v(c);return d===st?void 0:d}return Reflect.get(o,l,u)},getOwnPropertyDescriptor(o,l){var u=Reflect.getOwnPropertyDescriptor(o,l);if(u&&"value"in u){var c=n.get(l);c&&(u.value=v(c))}else if(u===void 0){var f=n.get(l),d=f==null?void 0:f.v;if(f!==void 0&&d!==st)return{enumerable:!0,configurable:!0,value:d,writable:!0}}return u},has(o,l){var d;if(l===un)return!0;var u=n.get(l),c=u!==void 0&&u.v!==st||Reflect.has(o,l);if(u!==void 0||z!==null&&(!c||(d=Hn(o,l))!=null&&d.writable)){u===void 0&&(u=s(()=>{var h=c?vt(o[l]):st,p=N(h);return p}),n.set(l,u));var f=v(u);if(f===st)return!1}return c},set(o,l,u,c){var x;var f=n.get(l),d=l in o;if(r&&l==="length")for(var h=u;hN(st)),n.set(h+"",p))}if(f===void 0)(!d||(x=Hn(o,l))!=null&&x.writable)&&(f=s(()=>N(void 0)),S(f,vt(u)),n.set(l,f));else{d=f.v!==st;var y=s(()=>vt(u));S(f,y)}var m=Reflect.getOwnPropertyDescriptor(o,l);if(m!=null&&m.set&&m.set.call(c,u),!d){if(r&&typeof l=="string"){var _=n.get("length"),A=Number(l);Number.isInteger(A)&&A>=_.v&&S(_,A+1)}Bn(a)}return!0},ownKeys(o){v(a);var l=Reflect.ownKeys(o).filter(f=>{var d=n.get(f);return d===void 0||d.v!==st});for(var[u,c]of n)c.v!==st&&!(u in o)&&l.push(u);return l},setPrototypeOf(){no()}})}var pa,Ki,Xi,Ji;function Ho(){if(pa===void 0){pa=window,Ki=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;Xi=Hn(t,"firstChild").get,Ji=Hn(t,"nextSibling").get,Ua(e)&&(e[sa]=void 0,e[hr]=null,e[oa]=void 0,e.__e=void 0),Ua(n)&&(n[Dn]=void 0)}}function ve(e=""){return document.createTextNode(e)}function Ae(e){return Xi.call(e)}function nr(e){return Ji.call(e)}function b(e,t){return Ae(e)}function Y(e,t=!1){{var n=Ae(e);return n instanceof Comment&&n.data===""?nr(n):n}}function k(e,t=1,n=!1){let r=e;for(;t--;)r=nr(r);return r}function zo(e){e.textContent=""}function Zi(){return!1}function Qi(e,t,n){return document.createElementNS(t??Oi,e,void 0)}let Ya=!1;function Wo(){Ya||(Ya=!0,document.addEventListener("reset",e=>{Promise.resolve().then(()=>{var t;if(!e.defaultPrevented)for(const n of e.target.elements)(t=n[pr])==null||t.call(n)})},{capture:!0}))}function Hr(e){var t=H,n=z;Ut(null),le(null);try{return e()}finally{Ut(t),le(n)}}function Bo(e,t,n,r=n){e.addEventListener(t,()=>Hr(n));const a=e[pr];a?e[pr]=()=>{a(),r(!0)}:e[pr]=()=>r(!0),Wo()}function $i(e){z===null&&(H===null&&$s(),Qs()),Oe&&Zs()}function Uo(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function ue(e,t){var n=z;n!==null&&(n.f&yt)!==0&&(e|=yt);var r={ctx:tt,deps:null,nodes:null,f:e|ut|zt,first:null,fn:t,last:null,next:null,parent:n,b:n&&n.b,prev:null,teardown:null,wv:0,ac:null};L==null||L.register_created_effect(r);var a=r;if((e&yn)!==0)sn!==null?sn.push(r):Ze.ensure().schedule(r);else if(t!==null){try{wn(r)}catch(s){throw xt(r),s}a.deps===null&&a.teardown===null&&a.nodes===null&&a.first===a.last&&(a.f&En)===0&&(a=a.first,(e&Jt)!==0&&(e&Xe)!==0&&a!==null&&(a.f|=Xe))}if(a!==null&&(a.parent=n,n!==null&&Uo(a,n),H!==null&&(H.f&ht)!==0&&(e&Se)===0)){var i=H;(i.effects??(i.effects=[])).push(a)}return r}function Oa(){return H!==null&&!Zt}function ts(e){const t=ue(tr,null);return nt(t,ot),t.teardown=e,t}function Pe(e){$i();var t=z.f,n=!H&&(t&$t)!==0&&(t&$e)===0;if(n){var r=tt;(r.e??(r.e=[])).push(e)}else return es(e)}function es(e){return ue(yn|Ci,e)}function Vo(e){return $i(),ue(tr|Ci,e)}function qo(e){Ze.ensure();const t=ue(Se|En,e);return(n={})=>new Promise(r=>{n.outro?Ye(t,()=>{xt(t),r(void 0)}):(xt(t),r(void 0))})}function Yo(e){return ue(yn,e)}function Go(e){return ue(ln|En,e)}function La(e,t=0){return ue(tr|t,e)}function F(e,t=[],n=[],r=[]){Mo(r,t,n,a=>{ue(tr,()=>e(...a.map(v)))})}function rr(e,t=0){var n=ue(Jt|t,e);return n}function Ft(e){return ue($t|En,e)}function ns(e){var t=e.teardown;if(t!==null){const n=Oe,r=H;Ga(!0),Ut(null);try{t.call(null)}finally{Ga(n),Ut(r)}}}function Ma(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){const a=n.ac;a!==null&&Hr(()=>{a.abort(Ir)});var r=n.next;(n.f&Se)!==0?n.parent=null:xt(n,t),n=r}}function Ko(e){for(var t=e.first;t!==null;){var n=t.next;(t.f&$t)===0&&xt(t),t=n}}function xt(e,t=!0){var n=!1;(t||(e.f&Gs)!==0)&&e.nodes!==null&&e.nodes.end!==null&&(rs(e.nodes.start,e.nodes.end),n=!0),nt(e,ia),Ma(e,t&&!n),Vn(e,0);var r=e.nodes&&e.nodes.t;if(r!==null)for(const i of r)i.stop();ns(e),e.f^=ia,e.f|=Qt;var a=e.parent;a!==null&&a.first!==null&&as(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=e.b=null}function rs(e,t){for(;e!==null;){var n=e===t?null:nr(e);e.remove(),e=n}}function as(e){var t=e.parent,n=e.prev,r=e.next;n!==null&&(n.next=r),r!==null&&(r.prev=n),t!==null&&(t.first===e&&(t.first=r),t.last===e&&(t.last=n))}function Ye(e,t,n=!0){var r=[];is(e,r,!0);var a=()=>{n&&xt(e),t&&t()},i=r.length;if(i>0){var s=()=>--i||a();for(var o of r)o.out(s)}else a()}function is(e,t,n){if((e.f&yt)===0){e.f^=yt;var r=e.nodes&&e.nodes.t;if(r!==null)for(const o of r)(o.is_global||n)&&t.push(o);for(var a=e.first;a!==null;){var i=a.next;if((a.f&Se)===0){var s=(a.f&Xe)!==0||(a.f&$t)!==0&&(e.f&Jt)!==0;is(a,t,s?n:!1)}a=i}}}function Na(e){ss(e,!0)}function ss(e,t){if((e.f&yt)!==0){e.f^=yt,(e.f&ot)===0&&(nt(e,ut),Ze.ensure().schedule(e));for(var n=e.first;n!==null;){var r=n.next,a=(n.f&Xe)!==0||(n.f&$t)!==0;ss(n,a?t:!1),n=r}var i=e.nodes&&e.nodes.t;if(i!==null)for(const s of i)(s.is_global||t)&&s.in()}}function Da(e,t){if(e.nodes)for(var n=e.nodes.start,r=e.nodes.end;n!==null;){var a=n===r?null:nr(n);t.append(n),n=a}}let br=!1,Oe=!1;function Ga(e){Oe=e}let H=null,Zt=!1;function Ut(e){H=e}let z=null;function le(e){z=e}let Wt=null;function os(e){H!==null&&(Wt===null?Wt=[e]:Wt.push(e))}let wt=null,Ct=0,Dt=null;function Xo(e){Dt=e}let ls=1,Re=0,Ge=Re;function Ka(e){Ge=e}function us(){return++ls}function ar(e){var t=e.f;if((t&ut)!==0)return!0;if(t&ht&&(e.f&=~Je),(t&oe)!==0){for(var n=e.deps,r=n.length,a=0;ae.wv)return!0}(t&zt)!==0&&dt===null&&nt(e,ot)}return!1}function cs(e,t,n=!0){var r=e.reactions;if(r!==null&&!(Wt!==null&&Ve.call(Wt,e)))for(var a=0;a{e.ac.abort(Ir)}),e.ac=null);try{e.f|=Ar;var c=e.fn,f=c();e.f|=$e;var d=e.deps,h=L==null?void 0:L.is_fork;if(wt!==null){var p;if(h||Vn(e,Ct),d!==null&&Ct>0)for(d.length=Ct+wt.length,p=0;pn==null?void 0:n.call(this,i))}return e.startsWith("pointer")||e.startsWith("touch")||e==="wheel"?Te(()=>{t.addEventListener(e,a,r)}):t.addEventListener(e,a,r),a}function ps(e,t,n,r,a){var i={capture:r,passive:a},s=el(e,t,n,i);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&ts(()=>{t.removeEventListener(e,s,i)})}function Z(e,t,n){(t[Ie]??(t[Ie]={}))[e]=n}function te(e){for(var t=0;t{throw A});throw d}}finally{e[Ie]=t,delete e.currentTarget,Ut(c),le(f)}}}var _i;const qr=((_i=globalThis==null?void 0:globalThis.window)==null?void 0:_i.trustedTypes)&&globalThis.window.trustedTypes.createPolicy("svelte-trusted-html",{createHTML:e=>e});function nl(e){return(qr==null?void 0:qr.createHTML(e))??e}function rl(e){var t=Qi("template");return t.innerHTML=nl(e.replaceAll("","")),t.content}function xn(e,t){var n=z;n.nodes===null&&(n.nodes={start:e,end:t,a:null,t:null})}function C(e,t){var n=(t&uo)!==0,r=(t&co)!==0,a,i=!e.startsWith("");return()=>{a===void 0&&(a=rl(i?e:""+e),n||(a=Ae(a)));var s=r||Ki?document.importNode(a,!0):a.cloneNode(!0);if(n){var o=Ae(s),l=s.lastChild;xn(o,l)}else xn(s,s);return s}}function it(e=""){{var t=ve(e+"");return xn(t,t),t}}function pt(){var e=document.createDocumentFragment(),t=document.createComment(""),n=ve();return e.append(t,n),xn(t,n),e}function w(e,t){e!==null&&e.before(t)}function j(e,t){var n=t==null?"":typeof t=="object"?`${t}`:t;n!==(e[Dn]??(e[Dn]=e.nodeValue))&&(e[Dn]=n,e.nodeValue=`${n}`)}function al(e,t){return il(e,t)}const fr=new Map;function il(e,{target:t,anchor:n,props:r={},events:a,context:i,intro:s=!0,transformError:o}){Ho();var l=void 0,u=qo(()=>{var c=n??t.appendChild(ve());Co(c,{pending:()=>{}},h=>{K({});var p=tt;i&&(p.c=i),a&&(r.$$events=a),l=e(h,r)||{},X()},o);var f=new Set,d=h=>{for(var p=0;p{var m;for(var h of f)for(const _ of[t,document]){var p=fr.get(_),y=p.get(h);--y==0?(_.removeEventListener(h,ya),p.delete(h),p.size===0&&fr.delete(_)):p.set(h,y)}ma.delete(d),c!==n&&((m=c.parentNode)==null||m.removeChild(c))}});return sl.set(l,u),l}let sl=new WeakMap;var Xt,ae,Ot,Ue,Qn,$n,jr;class ja{constructor(t,n=!0){_t(this,"anchor");R(this,Xt,new Map);R(this,ae,new Map);R(this,Ot,new Map);R(this,Ue,new Set);R(this,Qn,!0);R(this,$n,t=>{if(g(this,Xt).has(t)){var n=g(this,Xt).get(t),r=g(this,ae).get(n);if(r)Na(r),g(this,Ue).delete(n);else{var a=g(this,Ot).get(n);a&&(g(this,ae).set(n,a.effect),g(this,Ot).delete(n),a.fragment.lastChild.remove(),this.anchor.before(a.fragment),r=a.effect)}for(const[i,s]of g(this,Xt)){if(g(this,Xt).delete(i),i===t)break;const o=g(this,Ot).get(s);o&&(xt(o.effect),g(this,Ot).delete(s))}for(const[i,s]of g(this,ae)){if(i===n||g(this,Ue).has(i))continue;const o=()=>{if(Array.from(g(this,Xt).values()).includes(i)){var u=document.createDocumentFragment();Da(s,u),u.append(ve()),g(this,Ot).set(i,{effect:s,fragment:u})}else xt(s);g(this,Ue).delete(i),g(this,ae).delete(i)};g(this,Qn)||!r?(g(this,Ue).add(i),Ye(s,o,!1)):o()}}});R(this,jr,t=>{g(this,Xt).delete(t);const n=Array.from(g(this,Xt).values());for(const[r,a]of g(this,Ot))n.includes(r)||(xt(a.effect),g(this,Ot).delete(r))});this.anchor=t,D(this,Qn,n)}ensure(t,n){var r=L,a=Zi();if(n&&!g(this,ae).has(t)&&!g(this,Ot).has(t))if(a){var i=document.createDocumentFragment(),s=ve();i.append(s),g(this,Ot).set(t,{effect:Ft(()=>n(s)),fragment:i})}else g(this,ae).set(t,Ft(()=>n(this.anchor)));if(g(this,Xt).set(r,t),a){for(const[o,l]of g(this,ae))o===t?r.unskip_effect(l):r.skip_effect(l);for(const[o,l]of g(this,Ot))o===t?r.unskip_effect(l.effect):r.skip_effect(l.effect);r.oncommit(g(this,$n)),r.ondiscard(g(this,jr))}else g(this,$n).call(this,r)}}Xt=new WeakMap,ae=new WeakMap,Ot=new WeakMap,Ue=new WeakMap,Qn=new WeakMap,$n=new WeakMap,jr=new WeakMap;function W(e,t,n=!1){var r=new ja(e),a=n?Xe:0;function i(s,o){r.ensure(s,o)}rr(()=>{var s=!1;t((o,l=0)=>{s=!0,i(l,o)}),s||i(-1,null)},a)}const ol=Symbol("NaN");function ll(e,t,n){var r=new ja(e),a=!Tn();rr(()=>{var i=t();i!==i&&(i=ol),a&&i!==null&&typeof i=="object"&&(i={}),r.ensure(i,n)})}function ul(e,t){return t}function cl(e,t,n){for(var r=[],a=t.length,i,s=t.length,o=0;o{if(i){if(i.pending.delete(f),i.done.add(f),i.pending.size===0){var d=e.outrogroups;_a(e,Rr(i.done)),d.delete(i),d.size===0&&(e.outrogroups=null)}}else s-=1},!1)}if(s===0){var l=r.length===0&&n!==null;if(l){var u=n,c=u.parentNode;zo(c),c.append(u),e.items.clear()}_a(e,t,!l)}else i={pending:new Set(t),done:new Set},(e.outrogroups??(e.outrogroups=new Set)).add(i)}function _a(e,t,n=!0){var r;if(e.pending.size>0){r=new Set;for(const s of e.pending.values())for(const o of s)r.add(e.items.get(o).e)}for(var a=0;a{var x=n();return xi(x)?x:x==null?[]:Rr(x)}),d,h=new Map,p=!0;function y(x){(A.effect.f&Qt)===0&&(A.pending.delete(x),A.fallback=c,fl(A,d,s,t,r),c!==null&&(d.length===0?(c.f&se)===0?Na(c):(c.f^=se,Rn(c,null,s)):Ye(c,()=>{c=null})))}function m(x){A.pending.delete(x)}var _=rr(()=>{d=v(f);for(var x=d.length,E=new Set,O=L,P=Zi(),T=0;Ti(s)):(c=Ft(()=>i(Ja??(Ja=ve()))),c.f|=se)),x>E.size&&Js(),!p)if(h.set(O,E),P){for(const[ct,I]of o)E.has(ct)||O.skip_effect(I.e);O.oncommit(y),O.ondiscard(m)}else y(O);v(f)}),A={effect:_,items:o,pending:h,outrogroups:null,fallback:c};p=!1}function On(e){for(;e!==null&&(e.f&$t)===0;)e=e.next;return e}function fl(e,t,n,r,a){var $,ct,I,V,at,gt,Mt,ft,et;var i=(r&oo)!==0,s=t.length,o=e.items,l=On(e.effect.first),u,c=null,f,d=[],h=[],p,y,m,_;if(i)for(_=0;_0){var B=(r&Pi)!==0&&s===0?n:null;if(i){for(_=0;_{var Q,Nt;if(f!==void 0)for(m of f)(Nt=(Q=m.nodes)==null?void 0:Q.a)==null||Nt.apply()})}function dl(e,t,n,r,a,i,s,o){var l=(s&io)!==0?(s&lo)===0?Io(n,!1,!1):Qe(n):null,u=(s&so)!==0?Qe(a):null;return{v:l,i:u,e:Ft(()=>(i(t,l??n,u??a,o),()=>{e.delete(r)}))}}function Rn(e,t,n){if(e.nodes)for(var r=e.nodes.start,a=e.nodes.end,i=t&&(t.f&se)===0?t.nodes.start:n;r!==null;){var s=nr(r);if(i.before(r),r===a)return;r=s}}function ye(e,t,n){t===null?e.effect.first=n:t.next=n,n===null?e.effect.last=t:n.prev=t}function gs(e,t,n=!1,r=!1,a=!1,i=!1){var s=e,o="";if(n)var l=e;F(()=>{var u=z;if(o!==(o=t()??"")){if(n){u.nodes=null,l.innerHTML=o,o!==""&&xn(Ae(l),l.lastChild);return}if(u.nodes!==null&&(rs(u.nodes.start,u.nodes.end),u.nodes=null),o!==""){var c=r?fo:a?vo:void 0,f=Qi(r?"svg":a?"math":"template",c);f.innerHTML=o;var d=r||a?f:f.content;if(xn(Ae(d),d.lastChild),r||a)for(;Ae(d);)s.before(Ae(d));else s.before(d)}}})}function ir(e,t,...n){var r=new ja(e);rr(()=>{const a=t()??null;r.ensure(a,a&&(i=>a(i,...n)))},Xe)}const Za=[...` -\r\f \v\uFEFF`];function vl(e,t,n){var r=e==null?"":""+e;if(t&&(r=r?r+" "+t:t),n){for(var a of Object.keys(n))if(n[a])r=r?r+" "+a:a;else if(r.length)for(var i=a.length,s=0;(s=r.indexOf(a,s))>=0;){var o=s+i;(s===0||Za.includes(r[s-1]))&&(o===r.length||Za.includes(r[o]))?r=(s===0?"":r.substring(0,s))+r.substring(o+1):s=o}}return r===""?null:r}function hl(e,t){return e==null?null:String(e)}function Bt(e,t,n,r,a,i){var s=e[sa];if(s!==n||s===void 0){var o=vl(n,r,i);o==null?e.removeAttribute("class"):e.className=o,e[sa]=n}else if(i&&a!==i)for(var l in i){var u=!!i[l];(a==null||u!==!!a[l])&&e.classList.toggle(l,u)}return i}function pl(e,t,n,r){var a=e[oa];if(a!==t){var i=hl(t);i==null?e.removeAttribute("style"):e.style.cssText=i,e[oa]=t}return r}const gl=Symbol("is custom element"),ml=Symbol("is html");function Lt(e,t,n,r){var a=yl(e);a[t]!==(a[t]=n)&&(t==="loading"&&(e[Ks]=n),n==null?e.removeAttribute(t):typeof n!="string"&&_l(e).includes(t)?e[t]=n:e.setAttribute(t,n))}function yl(e){return e[hr]??(e[hr]={[gl]:e.nodeName.includes("-"),[ml]:e.namespaceURI===Oi})}var Qa=new Map;function _l(e){var t=e.getAttribute("is")||e.nodeName,n=Qa.get(t);if(n)return n;Qa.set(t,n=[]);for(var r,a=e,i=Element.prototype;i!==a;){r=ki(a);for(var s in r)r[s].set&&s!=="innerHTML"&&s!=="textContent"&&s!=="innerText"&&n.push(s);a=Ea(a)}return n}function qn(e,t,n=t){var r=new WeakSet;Bo(e,"input",async a=>{var i=a?e.defaultValue:e.value;if(i=Yr(e)?Gr(i):i,n(i),L!==null&&r.add(L),await Zo(),i!==(i=t())){var s=e.selectionStart,o=e.selectionEnd,l=e.value.length;if(e.value=i??"",o!==null){var u=e.value.length;s===o&&o===l&&u>l?(e.selectionStart=u,e.selectionEnd=u):(e.selectionStart=s,e.selectionEnd=Math.min(o,u))}}}),he(t)==null&&e.value&&(n(Yr(e)?Gr(e.value):e.value),L!==null&&r.add(L)),La(()=>{var a=t();if(e===document.activeElement){var i=L;if(r.has(i))return}Yr(e)&&a===Gr(e.value)||e.type==="date"&&!a&&!e.value||a!==e.value&&(e.value=a??"")})}function Yr(e){var t=e.type;return t==="number"||t==="range"}function Gr(e){return e===""?null:+e}function Kr(e,t){return e===t||(e==null?void 0:e[un])===t}function zr(e={},t,n,r){var a=tt.r,i=z;return Yo(()=>{var s,o;return La(()=>{s=o,o=[],he(()=>{Kr(n(...o),e)||(t(e,...o),s&&Kr(n(...s),e)&&t(null,...s))})}),()=>{let l=i;for(;l!==a&&l.parent!==null&&l.parent.f&ia;)l=l.parent;const u=()=>{o&&Kr(n(...o),e)&&t(null,...o)},c=l.teardown;l.teardown=()=>{u(),c==null||c()}}}),e}function bl(e=!1){const t=tt,n=t.l.u;if(!n)return;let r=()=>Qo(t.s);if(e){let a=0,i={};const s=Fr(()=>{let o=!1;const l=t.s;for(const u in l)l[u]!==i[u]&&(i[u]=l[u],o=!0);return o&&a++,a});r=()=>v(s)}n.b.length&&Vo(()=>{$a(t,r),ra(n.b)}),Pe(()=>{const a=he(()=>n.m.map(Ys));return()=>{for(const i of a)typeof i=="function"&&i()}}),n.a.length&&Pe(()=>{$a(t,r),ra(n.a)})}function $a(e,t){if(e.l.s)for(const n of e.l.s)v(n);t()}function kt(e,t,n,r){var a=r,i=!0,s=()=>(i&&(i=!1,a=r),a),o;o=e[t],o===void 0&&r!==void 0&&(o=s());var l;return l=()=>{var u=e[t];return u===void 0?s():(i=!0,u)},l}function ge(e){tt===null&&Si(),er&&tt.l!==null?wl(tt).m.push(e):Pe(()=>{const t=he(e);if(typeof t=="function")return t})}function ms(e){tt===null&&Si(),ge(()=>()=>he(e))}function wl(e){var t=e.l;return t.u??(t.u={a:[],b:[],m:[]})}const xl="5";var bi;typeof window<"u"&&((bi=window.__svelte??(window.__svelte={})).v??(bi.v=new Set)).add(xl);const ti="serena-dashboard-theme";function kl(){let e=N("light");function t(n){S(e,n,!0),document.documentElement.setAttribute("data-theme",n)}return{get current(){return v(e)},init(){var a;const n=localStorage.getItem(ti),r=(a=window.matchMedia)==null?void 0:a.call(window,"(prefers-color-scheme: dark)").matches;t(n??(r?"dark":"light"))},toggle(){const n=v(e)==="dark"?"light":"dark";t(n),localStorage.setItem(ti,n)}}}const Ke=kl();yo();var Al=C('');function El(e,t){K(t,!1),bl();var n=Al(),r=b(n),a=b(r),i=k(r,2),s=b(i);F(()=>{j(a,Ke.current==="dark"?"☀":"🌙"),j(s,Ke.current==="dark"?"Light":"Dark")}),Z("click",n,()=>Ke.toggle()),w(e,n),X()}te(["click"]);const Tl="https://oraios-software.de/serena-banners/manifest.php";function Cl(e,t){return e[t]??[]}function Sl(e,t){return t==="dark"?e.image_dark??e.image:e.image}async function Pl(){try{const e=await fetch(Tl);return e.ok?await e.json():{}}catch{return{}}}var Ol=C('
      ');function ys(e,t){K(t,!0);let n=N(vt([])),r=N(0),a=null;async function i(){S(n,Cl(await Pl(),t.target),!0),v(n).length&&S(r,Math.floor(Math.random()*v(n).length),!0),v(n).length>1&&(a=setInterval(()=>{S(r,(v(r)+1)%v(n).length)},6e3))}ge(()=>{i()}),ms(()=>{a&&clearInterval(a)});const s=J(()=>v(n)[v(r)]);var o=pt(),l=Y(o);{var u=c=>{var f=Ol(),d=b(f),h=b(d);F(p=>{Bt(f,1,`banner ${t.target??""}`,"svelte-1lo7lbo"),Lt(d,"href",v(s).link),Lt(h,"src",p),Lt(h,"alt",v(s).alt??"")},[()=>Sl(v(s),Ke.current)]),w(c,f)};W(l,c=>{v(s)&&c(u)})}w(e,o),X()}var Ll=C('
      ');function Ml(e,t){K(t,!0);const n=J(()=>Ke.current==="dark"?"serena-logo-dark-mode.svg":"serena-logo.svg");var r=Ll(),a=b(r),i=b(a),s=b(i),o=k(i,2),l=b(o);ys(l,{target:"platinum"});var u=k(a,2),c=b(u),f=b(c);El(f,{});var d=k(f,2),h=k(c,2),p=b(h);let y;var m=k(p,2);let _;var A=k(m,2);let x;F(()=>{Lt(s,"src",v(n)),y=Bt(p,1,"header-tab svelte-xjepwq",null,y,{active:t.active==="overview"}),_=Bt(m,1,"header-tab svelte-xjepwq",null,_,{active:t.active==="logs"}),x=Bt(A,1,"header-tab svelte-xjepwq",null,x,{active:t.active==="stats"})}),Z("click",d,()=>t.onshutdown()),Z("click",p,()=>t.onnavigate("overview")),Z("click",m,()=>t.onnavigate("logs")),Z("click",A,()=>t.onnavigate("stats")),w(e,r),X()}te(["click"]);function dr(e,t){let n=null,r=!1;const a=async()=>{if(!r){r=!0;try{await e()}finally{r=!1}}};return{start(){n||(a(),n=setInterval(()=>void a(),t))},stop(){n&&(clearInterval(n),n=null),r=!1}}}function Nl(e){switch(e){case"overview":return["config","queued","last"];case"logs":return["logs"];case"stats":return[]}}class Dl extends Error{constructor(t,n){super(n),this.status=t}}async function Ra(e){if(!e.ok)throw new Dl(e.status,`HTTP ${e.status} for ${e.url}`);return await e.json()}async function me(e){return Ra(await fetch(e))}async function Vt(e,t){return Ra(await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t??{})}))}async function jl(e){return Ra(await fetch(e,{method:"PUT"}))}const Rl=()=>me("/get_config_overview"),Il=e=>Vt("/get_log_messages",{start_idx:e}),Fl=()=>Vt("/clear_logs"),Hl=()=>me("/get_tool_names"),zl=()=>me("/get_tool_stats"),Wl=()=>Vt("/clear_tool_stats"),Bl=()=>me("/get_token_count_estimator_name"),Ul=()=>jl("/shutdown"),Vl=()=>me("/get_available_languages"),ql=e=>Vt("/add_language",{language:e}),Yl=e=>Vt("/remove_language",{language:e}),Gl=e=>Vt("/get_memory",{memory_name:e}),_s=(e,t)=>Vt("/save_memory",{memory_name:e,content:t}),Kl=e=>Vt("/delete_memory",{memory_name:e}),Xl=(e,t)=>Vt("/rename_memory",{old_name:e,new_name:t}),Jl=()=>me("/get_serena_config"),Zl=e=>Vt("/save_serena_config",{content:e}),Ql=()=>me("/queued_task_executions"),$l=e=>Vt("/cancel_task_execution",{task_id:e}),tu=()=>me("/last_execution"),eu=()=>me("/fetch_unread_news"),nu=e=>Vt("/mark_news_snippet_as_read",{news_snippet_id:e});function ru(){let e=N(null),t="";return{get data(){return v(e)},async poll(){const n=await Rl(),r=JSON.stringify(n);r!==t&&(S(e,n,!0),t=r)}}}const Cr=ru();async function pe(e){try{const t=await e();return t&&t.status==="error"?{ok:!1,message:t.message??"Request failed",data:t}:{ok:!0,data:t}}catch(t){return{ok:!1,message:t instanceof Error?t.message:"Request failed"}}}function au(){let e=N(vt([])),t=N(null),n=N(vt([])),r=N("");const a=new Set;return{get queued(){return v(e)},get last(){return v(t)},get cancelled(){return v(n)},get cancelError(){return v(r)},clearCancelError(){S(r,"")},async pollQueued(){S(e,(await Ql()).queued_executions,!0)},async pollLast(){const i=(await tu()).last_execution;S(t,i&&i.logged?i:null,!0)},async cancel(i){if(a.has(i.task_id))return{ok:!1};a.add(i.task_id),S(r,"");try{const s=await pe(()=>$l(i.task_id));return s.ok?(v(n).some(o=>o.task_id===i.task_id)||S(n,[...v(n),i],!0),s):(S(r,s.message??"Failed to cancel execution",!0),s)}finally{a.delete(i.task_id)}}}}const ie=au();function iu(){let e=N(vt([])),t=N(0),n=N(null);return{get lines(){return v(e)},get activeProject(){return v(n)},async poll(){const r=await Il(v(t));r.messages.length&&S(e,[...v(e),...r.messages],!0),S(t,r.max_idx,!0),S(n,r.active_project,!0)},async clear(){await Fl(),S(e,[],!0),S(t,0)}}}const wr=iu();function su(e){return e?`${e} – Serena Dashboard`:"Serena Dashboard"}var ou=C('
      '),lu=C('

      ');function Sr(e,t){let n=kt(t,"open",3,!1),r=N(vt(n()));function a(){S(r,!v(r))}var i=lu(),s=b(i),o=b(s),l=b(o),u=b(l),c=k(l,2);let f;var d=k(s,2);{var h=p=>{var y=ou(),m=b(y);ir(m,()=>t.children),w(p,y)};W(d,p=>{v(r)&&p(h)})}F(()=>{Lt(o,"aria-expanded",v(r)),j(u,t.title),f=Bt(c,1,"toggle-icon svelte-oy8gea",null,f,{open:v(r)})}),Z("click",o,a),w(e,i)}te(["click"]);var uu=C("");function At(e,t){let n=kt(t,"variant",3,"primary"),r=kt(t,"disabled",3,!1);var a=uu(),i=b(a);ir(i,()=>t.children),F(()=>{Bt(a,1,`btn ${n()??""}`,"svelte-18f749u"),a.disabled=r()}),Z("click",a,function(...s){var o;(o=t.onclick)==null||o.apply(this,s)}),w(e,a)}te(["click"]);var cu=C(" "),fu=C(''),du=C(' '),vu=C(' '),hu=C(" ",1),pu=C(' '),gu=C('
      '),mu=C('
      '),yu=C('
      '),_u=C('
      Version Active Project Languages Context Active Modes File Encoding
      ');function bu(e,t){K(t,!0);var n=_u(),r=b(n),a=k(b(r),2),i=b(a),s=k(a,4),o=b(s);{var l=I=>{var V=cu(),at=b(V);F(()=>{Lt(V,"title",`Project configuration in ${t.data.active_project.path??""}/.serena/project.yml`),j(at,t.data.active_project.name)}),w(I,V)},u=I=>{var V=it();F(()=>{var at;return j(V,((at=t.data.active_project)==null?void 0:at.name)??"None")}),w(I,V)};W(o,I=>{var V,at;(V=t.data.active_project)!=null&&V.name&&((at=t.data.active_project)!=null&&at.path)?I(l):I(u,-1)})}var c=k(s,4),f=b(c);{var d=I=>{var V=it("Using JetBrains backend");w(I,V)},h=I=>{var V=vu(),at=b(V);Ht(at,16,()=>t.data.languages,ft=>ft,(ft,et)=>{var Q=du(),Nt=b(Q),Ne=k(Nt);{var Sn=ee=>{var Pn=fu();F(()=>Lt(Pn,"aria-label",`Remove ${et??""}`)),Z("click",Pn,()=>t.onremovelanguage(et)),w(ee,Pn)};W(Ne,ee=>{t.data.languages.length>1&&ee(Sn)})}F(()=>j(Nt,`${et??""} `)),w(ft,Q)});var gt=k(at,2);{var Mt=ft=>{At(ft,{variant:"secondary",get onclick(){return t.onaddlanguage},children:(et,Q)=>{var Nt=it("Add Language");w(et,Nt)},$$slots:{default:!0}})};W(gt,ft=>{var et;(et=t.data.active_project)!=null&&et.name&&ft(Mt)})}w(I,V)};W(f,I=>{t.data.jetbrains_mode?I(d):I(h,-1)})}var p=k(c,4),y=b(p),m=b(y),_=k(p,4),A=b(_);{var x=I=>{var V=pt(),at=Y(V);Ht(at,19,()=>t.data.modes,gt=>gt.name,(gt,Mt,ft)=>{var et=hu(),Q=Y(et),Nt=b(Q),Ne=k(Q);{var Sn=ee=>{var Pn=it(",");w(ee,Pn)};W(Ne,ee=>{v(ft){Lt(Q,"title",v(Mt).path),j(Nt,v(Mt).name)}),w(gt,et)}),w(I,V)},E=I=>{var V=it("None");w(I,V)};W(A,I=>{t.data.modes.length?I(x):I(E,-1)})}var O=k(_,4),P=b(O),T=k(r,2);Sr(T,{get title(){return`Active Tools (${t.data.active_tools.length??""})`},children:(I,V)=>{var at=gu();Ht(at,20,()=>t.data.active_tools,gt=>gt,(gt,Mt)=>{var ft=pu(),et=b(ft);F(()=>j(et,Mt)),w(gt,ft)}),w(I,at)},$$slots:{default:!0}});var M=k(T,2);{var B=I=>{Sr(I,{get title(){return`Memories (${t.data.available_memories.length??""})`},children:(V,at)=>{var gt=yu(),Mt=b(gt);Ht(Mt,16,()=>t.data.available_memories,et=>et,(et,Q)=>{var Nt=mu(),Ne=b(Nt),Sn=b(Ne),ee=k(Ne,2);F(()=>{j(Sn,Q),Lt(ee,"aria-label",`Delete memory ${Q??""}`)}),Z("click",Ne,()=>t.onopenmemory(Q)),Z("click",ee,()=>t.ondeletememory(Q)),w(et,Nt)});var ft=k(Mt,2);Z("click",ft,function(...et){var Q;(Q=t.oncreatememory)==null||Q.apply(this,et)}),w(V,gt)},$$slots:{default:!0}})};W(M,I=>{t.data.available_memories&&I(B)})}var $=k(M,2),ct=k(b($),2);At(ct,{variant:"secondary",get onclick(){return t.oneditconfig},children:(I,V)=>{var at=it("Edit Global Serena Config");w(I,at)},$$slots:{default:!0}}),F(()=>{j(i,t.data.serena_version),Lt(y,"title",t.data.context.path),j(m,t.data.context.name),j(P,t.data.encoding??"N/A")}),w(e,n),X()}te(["click"]);var wu=C('
      '),xu=C('
      '),ku=C('
      No tool usage yet.
      ');function Au(e,t){K(t,!0);const n=J(()=>Object.entries(t.stats).sort((l,u)=>u[1].num_calls-l[1].num_calls)),r=J(()=>Math.max(1,...v(n).map(([,l])=>l.num_calls)));var a=pt(),i=Y(a);{var s=l=>{var u=xu();Ht(u,21,()=>v(n),([c,f])=>c,(c,f)=>{var d=J(()=>Ei(v(f),2));let h=()=>v(d)[0],p=()=>v(d)[1];var y=wu(),m=b(y),_=b(m),A=k(m,2),x=b(A),E=k(A,2),O=b(E);F(()=>{j(_,h()),pl(x,`width:${p().num_calls/v(r)*100}%`),j(O,p().num_calls)}),w(c,y)}),w(l,u)},o=l=>{var u=ku();w(l,u)};W(i,l=>{v(n).length?l(s):l(o,-1)})}w(e,a),X()}var Eu=C('

      '),Tu=C('
      ');function _e(e,t){let n=kt(t,"title",3,"");var r=Tu(),a=b(r);{var i=o=>{var l=Eu(),u=b(l);F(()=>j(u,n())),w(o,l)};W(a,o=>{n()&&o(i)})}var s=k(a,2);ir(s,()=>t.children),w(e,r)}var Cu=C("
    • "),Su=C('
        '),Pu=C('
        None.
        ');function Xr(e,t){K(t,!0),_e(e,{children:(n,r)=>{Sr(n,{get title(){return t.title},children:(a,i)=>{var s=pt(),o=Y(s);{var l=c=>{var f=Su();Ht(f,21,()=>t.items,d=>d.name,(d,h)=>{var p=Cu();let y;var m=b(p);F(()=>{y=Bt(p,1,"svelte-1mlfgti",null,y,{active:v(h).active}),j(m,v(h).name)}),w(d,p)}),w(c,f)},u=c=>{var f=Pu();w(c,f)};W(o,c=>{t.items.length?c(l):c(u,-1)})}w(a,s)},$$slots:{default:!0}})},$$slots:{default:!0}}),X()}var Ou=C('
      • '),Lu=C('
          '),Mu=C('
          None.
          ');function Nu(e,t){K(t,!0),_e(e,{children:(n,r)=>{Sr(n,{get title(){return`Registered Projects (${t.projects.length??""})`},open:!0,children:(a,i)=>{var s=pt(),o=Y(s);{var l=c=>{var f=Lu();Ht(f,21,()=>t.projects,d=>d.path,(d,h)=>{var p=Ou();let y;var m=b(p),_=b(m),A=k(m,2),x=b(A);F(()=>{y=Bt(p,1,"project svelte-19jixoo",null,y,{active:v(h).is_active}),j(_,v(h).name),j(x,v(h).path)}),w(d,p)}),w(c,f)},u=c=>{var f=Mu();w(c,f)};W(o,c=>{t.projects.length?c(l):c(u,-1)})}w(a,s)},$$slots:{default:!0}})},$$slots:{default:!0}}),X()}var Du=C('
          ');function sr(e){var t=Du();w(e,t)}var ju=C('
          '),Ru=C('
          '),Iu=C('
          No queued executions.
          '),Fu=C(''),Hu=C(" ",1);function zu(e,t){K(t,!0);let n=kt(t,"cancelError",3,"");const r=J(()=>t.items.filter(c=>c.logged));var a=Hu(),i=Y(a);{var s=c=>{var f=Ru();Ht(f,21,()=>v(r),d=>d.task_id,(d,h)=>{var p=ju();let y;var m=b(p);{var _=O=>{sr(O)};W(m,O=>{v(h).is_running&&O(_)})}var A=k(m,2),x=b(A),E=k(A,2);F(()=>{y=Bt(p,1,"execution-item svelte-1wrcimm",null,y,{running:v(h).is_running}),j(x,v(h).name),Lt(E,"aria-label",`Cancel ${v(h).name??""}`)}),Z("click",E,()=>t.oncancelexecution(v(h))),w(d,p)}),w(c,f)},o=c=>{var f=Iu();w(c,f)};W(i,c=>{v(r).length?c(s):c(o,-1)})}var l=k(i,2);{var u=c=>{var f=Fu(),d=b(f);F(()=>j(d,n())),w(c,f)};W(l,c=>{n()&&c(u)})}w(e,a),X()}te(["click"]);var Wu=C('
          '),Bu=C('
          None yet.
          ');function Uu(e,t){K(t,!0);const n=J(()=>t.execution?t.execution.is_running?"Running":t.execution.finished_successfully?"Succeeded":"Failed":""),r=J(()=>t.execution?t.execution.is_running?"…":t.execution.finished_successfully?"✓":"✗":"");var a=pt(),i=Y(a);{var s=l=>{var u=Wu();let c;var f=b(u),d=b(f),h=k(f,2),p=b(h),y=b(p),m=k(p,2),_=b(m),A=k(h,2),x=b(A);F(()=>{c=Bt(u,1,"last-exec svelte-1772y66",null,c,{ok:t.execution.finished_successfully&&!t.execution.is_running,fail:!t.execution.finished_successfully&&!t.execution.is_running,running:t.execution.is_running}),j(d,v(r)),j(y,v(n)),j(_,t.execution.name),j(x,`#${t.execution.task_id??""}`)}),w(l,u)},o=l=>{var u=Bu();w(l,u)};W(i,l=>{t.execution?l(s):l(o,-1)})}w(e,a),X()}var Vu=C('
          '),qu=C('
          ');function Yu(e,t){var n=qu();Ht(n,21,()=>t.items,r=>r.task_id,(r,a)=>{var i=Vu();let s;var o=b(i),l=b(o),u=k(o,2),c=b(u),f=k(u,2),d=b(f);F(()=>{s=Bt(i,1,"cancelled-item svelte-1re8sys",null,s,{abandoned:v(a).is_running}),j(l,v(a).is_running?"!":"✕"),j(c,v(a).name),j(d,`${v(a).is_running?"abandoned · ":""}#${v(a).task_id??""}`)}),w(r,i)}),w(e,n)}function Gu(e){return Object.entries(e).sort((t,n)=>n[0].localeCompare(t[0]))}var Ku=C('
          '),Xu=C(`

          What's New

          `);function Ju(e,t){K(t,!0);let n=N(vt([]));async function r(){S(n,Gu((await eu()).news),!0)}ge(()=>{r()});async function a(l){await nu(l),S(n,v(n).filter(([u])=>u!==l),!0)}var i=pt(),s=Y(i);{var o=l=>{var u=Xu(),c=k(b(u),2);Ht(c,17,()=>v(n),([f,d])=>f,(f,d)=>{var h=J(()=>Ei(v(d),2));let p=()=>v(h)[0],y=()=>v(h)[1];var m=Ku(),_=b(m);gs(_,y,!0);var A=k(_,2);Z("click",A,()=>a(p())),w(f,m)}),w(l,u)};W(s,l=>{v(n).length&&l(o)})}w(e,i),X()}te(["click"]);var Zu=C('
          ');function Qu(e,t){K(t,!0);const n=J(()=>Cr.data);var r=pt(),a=Y(r);{var i=o=>{sr(o)},s=o=>{var l=Zu(),u=b(l),c=b(u);Ju(c,{});var f=k(c,2);_e(f,{title:"Current Configuration",children:(T,M)=>{bu(T,{get data(){return v(n)},get onaddlanguage(){return t.onaddlanguage},get onremovelanguage(){return t.onremovelanguage},get oneditconfig(){return t.oneditconfig},get onopenmemory(){return t.onopenmemory},get oncreatememory(){return t.oncreatememory},get ondeletememory(){return t.ondeletememory}})},$$slots:{default:!0}});var d=k(f,2);_e(d,{title:"Tool Usage",children:(T,M)=>{Au(T,{get stats(){return v(n).tool_stats_summary}})},$$slots:{default:!0}});var h=k(d,2);_e(h,{title:"Executions Queue",children:(T,M)=>{zu(T,{get items(){return ie.queued},get cancelError(){return ie.cancelError},get oncancelexecution(){return t.oncancelexecution}})},$$slots:{default:!0}});var p=k(h,2);{var y=T=>{_e(T,{title:"Cancelled Executions",children:(M,B)=>{Yu(M,{get items(){return ie.cancelled}})},$$slots:{default:!0}})};W(p,T=>{ie.cancelled.length&&T(y)})}var m=k(p,2);_e(m,{title:"Last Execution",children:(T,M)=>{Uu(T,{get execution(){return ie.last}})},$$slots:{default:!0}});var _=k(u,2),A=b(_);Nu(A,{get projects(){return v(n).registered_projects}});var x=k(A,2);{let T=J(()=>v(n).available_tools.filter(M=>!M.is_active).map(M=>({name:M.name})));Xr(x,{title:"Available Tools (Disabled)",get items(){return v(T)}})}var E=k(x,2);{let T=J(()=>v(n).available_modes.map(M=>({name:M.name,active:M.is_active})));Xr(E,{title:"Available Modes",get items(){return v(T)}})}var O=k(E,2);{let T=J(()=>v(n).available_contexts.map(M=>({name:M.name,active:M.is_active})));Xr(O,{title:"Available Contexts",get items(){return v(T)}})}var P=k(O,2);_e(P,{children:(T,M)=>{ys(T,{target:"gold"})},$$slots:{default:!0}}),w(o,l)};W(a,o=>{v(n)?o(s,-1):o(i)})}w(e,r),X()}var $u=C('
          ');function tc(e,t){K(t,!0);let n=N(!1);const r=J(()=>t.lines.length===0);async function a(){try{await navigator.clipboard.writeText(t.lines.join(` -`)),S(n,!0),setTimeout(()=>S(n,!1),1e3)}catch{}}function i(){const f=new Blob([t.lines.join(` -`)],{type:"text/plain"}),d=URL.createObjectURL(f),h=document.createElement("a");h.href=d,h.download="serena-logs.txt",h.click(),setTimeout(()=>URL.revokeObjectURL(d),0)}var s=$u(),o=b(s),l=b(o),u=k(o,2),c=k(u,2);F(()=>{o.disabled=v(r),j(l,v(n)?"✓ copied":"copy logs"),u.disabled=v(r),c.disabled=v(r)}),Z("click",o,a),Z("click",u,i),Z("click",c,function(...f){var d;(d=t.onclear)==null||d.apply(this,f)}),w(e,s),X()}te(["click"]);function ec(e){return e.startsWith("DEBUG")?"debug":e.startsWith("INFO")?"info":e.startsWith("WARNING")?"warning":e.startsWith("ERROR")?"error":"info"}function ei(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function nc(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function rc(e,t){const n=ei(e);if(t.length===0)return n;const r=[...t].sort((s,o)=>o.length-s.length),a=new Map(r.map(s=>[s.toLowerCase(),s])),i=new RegExp(`\\b(?:${r.map(nc).join("|")})\\b`,"gi");return n.replace(i,s=>{const o=a.get(s.toLowerCase())??s;return`${ei(o)}`})}var ac=C("
          "),ic=C('
          ');function sc(e,t){K(t,!0);let n=N(null);Pe(()=>{if(t.lines.length,!v(n))return;v(n).scrollHeight-v(n).scrollTop-v(n).clientHeight<40&&queueMicrotask(()=>{v(n)&&(v(n).scrollTop=v(n).scrollHeight)})});var r=ic();Ht(r,21,()=>t.lines,ul,(a,i)=>{var s=ac();gs(s,()=>rc(v(i),t.toolNames),!0),F(o=>Bt(s,1,`log-line ${o??""}`,"svelte-1f7p4v8"),[()=>ec(v(i))]),w(a,s)}),zr(r,a=>S(n,a),()=>v(n)),w(e,r),X()}var oc=C(" ",1);function lc(e,t){K(t,!0);let n=N(vt([]));ge(()=>{(async()=>S(n,(await Hl()).tool_names,!0))()});var r=oc(),a=Y(r);tc(a,{get lines(){return wr.lines},onclear:()=>wr.clear()});var i=k(a,2);sc(i,{get lines(){return wr.lines},get toolNames(){return v(n)}}),w(e,r),X()}function uc(){let e=N(vt({})),t=N("unknown");return{get stats(){return v(e)},get estimator(){return v(t)},async refresh(){S(e,(await zl()).stats,!0),S(t,(await Bl()).token_count_estimator_name,!0)},async clear(){await Wl(),S(e,{},!0)}}}const qt=uc();function bs(e,t){return Object.entries(e).sort((n,r)=>r[1][t]-n[1][t])}function Jr(e,t){const n=bs(e,t);return{labels:n.map(([r])=>r),datasets:[{values:n.map(([,r])=>r[t])}]}}function ni(e,t,n){const r=bs(e,t);return{labels:r.map(([a])=>a),datasets:[{name:n,values:r.map(([,a])=>a[t])}]}}function cc(e,t){t===void 0&&(t={});var n=t.insertAt;if(typeof document<"u"){var r=document.head||document.getElementsByTagName("head")[0],a=document.createElement("style");a.type="text/css",n==="top"&&r.firstChild?r.insertBefore(a,r.firstChild):r.appendChild(a),a.styleSheet?a.styleSheet.cssText=e:a.appendChild(document.createTextNode(e))}}function Ce(e,t){return typeof e=="string"?(t||document).querySelector(e):e||null}function Yn(e){var t=e.getBoundingClientRect();return{top:t.top+(document.documentElement.scrollTop||document.body.scrollTop),left:t.left+(document.documentElement.scrollLeft||document.body.scrollLeft)}}function fc(e){return e.offsetParent===null}function dc(e){var t=e.getBoundingClientRect();return t.top>=0&&t.left>=0&&t.bottom<=(window.innerHeight||document.documentElement.clientHeight)&&t.right<=(window.innerWidth||document.documentElement.clientWidth)}function vc(e){var t=window.getComputedStyle(e),n=parseFloat(t.paddingLeft)+parseFloat(t.paddingRight);return e.clientWidth-n}function hc(e,t,n){var r=document.createEvent("HTMLEvents");r.initEvent(t,!0,!0);for(var a in n)r[a]=n[a];return e.dispatchEvent(r)}function Pr(e){return e.titleHeight+e.margins.top+e.paddings.top}function xr(e){return e.margins.left+e.paddings.left}function ws(e){return e.margins.top+e.margins.bottom+e.paddings.top+e.paddings.bottom+e.titleHeight+e.legendHeight}function Or(e){return e.margins.left+e.margins.right+e.paddings.left+e.paddings.right}function pc(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function xs(e){return parseFloat(e.toFixed(2))}function ba(e,t,n){var r=arguments.length>3&&arguments[3]!==void 0&&arguments[3];n||(n=r?e[0]:e[e.length-1]);var a=new Array(Math.abs(t)).fill(n);return e=r?a.concat(e):e.concat(a)}function ks(e,t){return(e+"").length*t}function cn(e,t){return{x:Math.sin(e*fi)*t,y:Math.cos(e*fi)*t}}function on(e){var t=arguments.length>1&&arguments[1]!==void 0&&arguments[1];return!Number.isNaN(e)&&e!==void 0&&!!Number.isFinite(e)&&!(t&&e<0)}function gc(e){return+(Math.round(e+"e4")+"e-4")}function As(e){var t=void 0,n=void 0,r=void 0;if(e instanceof Date)return new Date(e.getTime());if((e===void 0?"undefined":Zf(e))!=="object"||e===null)return e;t=Array.isArray(e)?[]:{};for(r in e)n=e[r],t[r]=As(n);return t}function Es(e,t){var n=void 0,r=void 0;return e<=t?(n=t-e,r=e):(n=e-t,r=t),[n,r]}function Et(e,t){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:t.length-e.length;return n>0?e=ba(e,n):t=ba(t,n),[e,t]}function Ia(e,t){if(e)return e.length>t?e.slice(0,t-3)+"...":e}function mc(e){var t=void 0;if(typeof e=="number")t=e;else if(typeof e=="string"&&(t=Number(e),Number.isNaN(t)))return e;var n=Math.floor(Math.log10(Math.abs(t)));if(n<=2)return t;var r=Math.floor(n/3),a=Math.pow(10,n-3*r)*+(t/Math.pow(10,n)).toFixed(1);return Math.round(100*a)/100+" "+["","K","M","B","T"][r]}function Ts(e,t){for(var n=[],r=0;r255?255:e<0?0:e}function Fa(e,t){var n=Ds(e),r=!1;n[0]=="#"&&(n=n.slice(1),r=!0);var a=parseInt(n,16),i=Zr((a>>16)+t),s=Zr((a>>8&255)+t),o=Zr((255&a)+t);return(r?"#":"")+(o|s<<8|i<<16).toString(16)}function yc(e){var t=/(^\s*)(rgb|hsl)(a?)[(]\s*([\d.]+\s*%?)\s*,\s*([\d.]+\s*%?)\s*,\s*([\d.]+\s*%?)\s*(?:,\s*([\d.]+)\s*)?[)]$/i;return/(^\s*)(#)((?:[A-Fa-f0-9]{3}){1,2})$/i.test(e)||t.test(e)}function ri(e,t){return typeof e=="string"?(t||document).querySelector(e):e||null}function G(e,t){var n=document.createElementNS("http://www.w3.org/2000/svg",e);for(var r in t){var a=t[r];if(r==="inside")ri(a).appendChild(n);else if(r==="around"){var i=ri(a);i.parentNode.insertBefore(n,i),n.appendChild(i)}else r==="styles"?(a===void 0?"undefined":td(a))==="object"&&Object.keys(a).map(function(s){n.style[s]=a[s]}):(r==="className"&&(r="class"),r==="innerHTML"?n.textContent=a:n.setAttribute(r,a))}return n}function _c(e,t){return G("linearGradient",{inside:e,id:t,x1:0,x2:0,y1:0,y2:1})}function Qr(e,t,n,r){return G("stop",{inside:e,style:"stop-color: "+n,offset:t,"stop-opacity":r})}function bc(e,t,n,r){return G("svg",{className:t,inside:e,width:n,height:r})}function wc(e){return G("defs",{inside:e})}function wa(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:"",n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:void 0,r={className:e,transform:t};return n&&(r.inside=n),G("g",r)}function Lr(e){return G("path",{className:arguments.length>1&&arguments[1]!==void 0?arguments[1]:"",d:e,styles:{stroke:arguments.length>2&&arguments[2]!==void 0?arguments[2]:"none",fill:arguments.length>3&&arguments[3]!==void 0?arguments[3]:"none","stroke-width":arguments.length>4&&arguments[4]!==void 0?arguments[4]:2}})}function xc(e,t,n,r){var a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:1,i=arguments.length>5&&arguments[5]!==void 0?arguments[5]:0,s=n.x+e.x,o=n.y+e.y,l=n.x+t.x,u=n.y+t.y;return"M"+n.x+" "+n.y+` - L`+s+" "+o+` - A `+r+" "+r+" 0 "+i+" "+(a?1:0)+` - `+l+" "+u+" z"}function kc(e,t,n,r){var a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:1,i=arguments.length>5&&arguments[5]!==void 0?arguments[5]:0,s=n.x+e.x,o=n.y+e.y,l=n.x+t.x,u=2*n.y,c=n.y+t.y;return"M"+n.x+" "+n.y+` - L`+s+" "+o+` - A `+r+" "+r+" 0 "+i+" "+(a?1:0)+` - `+l+" "+u+` z - L`+s+" "+u+` - A `+r+" "+r+" 0 "+i+" "+(a?1:0)+` - `+l+" "+c+" z"}function Ac(e,t,n,r){var a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:1,i=arguments.length>5&&arguments[5]!==void 0?arguments[5]:0,s=n.x+e.x,o=n.y+e.y,l=n.x+t.x,u=n.y+t.y;return"M"+s+" "+o+` - A `+r+" "+r+" 0 "+i+" "+(a?1:0)+` - `+l+" "+u}function Ec(e,t,n,r){var a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:1,i=arguments.length>5&&arguments[5]!==void 0?arguments[5]:0,s=n.x+e.x,o=n.y+e.y,l=n.x+t.x,u=2*r+o,c=n.y+e.y;return"M"+s+" "+o+` - A `+r+" "+r+" 0 "+i+" "+(a?1:0)+` - `+l+" "+u+` - M`+s+" "+u+` - A `+r+" "+r+" 0 "+i+" "+(a?1:0)+` - `+l+" "+c}function ai(e,t){var n=arguments.length>2&&arguments[2]!==void 0&&arguments[2],r="path-fill-gradient-"+t+"-"+(n?"lighter":"default"),a=_c(e,r),i=[1,.6,.2];return n&&(i=[.4,.2,0]),Qr(a,"0%",t,i[0]),Qr(a,"50%",t,i[1]),Qr(a,"100%",t,i[2]),r}function Tc(e,t,n,r){var a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:Ms,i=arguments.length>5&&arguments[5]!==void 0?arguments[5]:"none";return G("rect",{className:"percentage-bar",x:e,y:t,width:n,height:r,fill:i,styles:{stroke:Fa(i,-25),"stroke-dasharray":"0, "+(r+n)+", "+n+", "+r,"stroke-width":a}})}function Cs(e,t,n,r,a){var i=arguments.length>5&&arguments[5]!==void 0?arguments[5]:"none",s=arguments.length>6&&arguments[6]!==void 0?arguments[6]:{},o={className:e,x:t,y:n,width:r,height:r,rx:a,fill:i};return Object.keys(s).map(function(l){o[l]=s[l]}),G("rect",o)}function Cc(e,t,n){var r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:"none",a=arguments[4];a=arguments.length>5&&arguments[5]!==void 0&&arguments[5]?Ia(a,js):a;var i={className:"legend-bar",x:0,y:0,width:n,height:"2px",fill:r},s=G("text",{className:"legend-dataset-text",x:0,y:0,dy:2*lt+"px","font-size":1.2*lt+"px","text-anchor":"start",fill:Ha,innerHTML:a}),o=G("g",{transform:"translate("+e+", "+t+")"});return o.appendChild(G("rect",i)),o.appendChild(s),o}function Sc(e,t,n){var r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:"none",a=arguments[4];a=arguments.length>5&&arguments[5]!==void 0&&arguments[5]?Ia(a,js):a;var i={className:"legend-dot",cx:0,cy:0,r:n,fill:r},s=G("text",{className:"legend-dataset-text",x:0,y:0,dx:lt+"px",dy:lt/3+"px","font-size":1.2*lt+"px","text-anchor":"start",fill:Ha,innerHTML:a}),o=G("g",{transform:"translate("+e+", "+t+")"});return o.appendChild(G("circle",i)),o.appendChild(s),o}function Un(e,t,n,r){var a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:{},i=a.fontSize||lt;return G("text",{className:e,x:t,y:n,dy:(a.dy!==void 0?a.dy:i/2)+"px","font-size":i+"px",fill:a.fill||Ha,"text-anchor":a.textAnchor||"start",innerHTML:r})}function Pc(e,t,n,r){var a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:{};a.stroke||(a.stroke=Cn);var i=G("line",{className:"line-vertical "+a.className,x1:0,x2:0,y1:n,y2:r,styles:{stroke:a.stroke}}),s=G("text",{x:0,y:n>r?n+Le:n-Le-lt,dy:lt+"px","font-size":lt+"px","text-anchor":"middle",innerHTML:t+""}),o=G("g",{transform:"translate("+e+", 0)"});return o.appendChild(i),o.appendChild(s),o}function Ss(e,t,n,r){var a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:{};a.stroke||(a.stroke=Cn),a.lineType||(a.lineType=""),a.shortenNumbers&&(t=mc(t));var i=G("line",{className:"line-horizontal "+a.className+(a.lineType==="dashed"?"dashed":""),x1:n,x2:r,y1:0,y2:0,styles:{stroke:a.stroke}}),s=G("text",{x:n3&&arguments[3]!==void 0?arguments[3]:{};on(e)||(e=0),r.pos||(r.pos="left"),r.offset||(r.offset=0),r.mode||(r.mode="span"),r.stroke||(r.stroke=Cn),r.className||(r.className="");var a=-1*fn,i=r.mode==="span"?n+fn:0;return r.mode==="tick"&&r.pos==="right"&&(a=n+fn,i=n),a+=r.offset,i+=r.offset,Ss(e,t,a,i,{stroke:r.stroke,className:r.className,lineType:r.lineType,shortenNumbers:r.shortenNumbers})}function Lc(e,t,n){var r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:{};on(e)||(e=0),r.pos||(r.pos="bottom"),r.offset||(r.offset=0),r.mode||(r.mode="span"),r.stroke||(r.stroke=Cn),r.className||(r.className="");var a=n+fn,i=r.mode==="span"?-1*fn:n;return r.mode==="tick"&&r.pos==="top"&&(a=-1*fn,i=0),Pc(e,t,a,i,{stroke:r.stroke,className:r.className,lineType:r.lineType})}function Mc(e,t,n){var r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:{};r.labelPos||(r.labelPos="right");var a=G("text",{className:"chart-label",x:r.labelPos==="left"?Le:n-ks(t,5)-Le,y:0,dy:lt/-2+"px","font-size":lt+"px","text-anchor":"start",innerHTML:t+""}),i=Ss(e,"",0,n,{stroke:r.stroke||Cn,className:r.className||"",lineType:r.lineType});return i.appendChild(a),i}function Nc(e,t,n,r){var a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:{},i=e-t,s=G("rect",{className:"bar mini",styles:{fill:"rgba(228, 234, 239, 0.49)",stroke:Cn,"stroke-dasharray":n+", "+i},x:0,y:0,width:n,height:i});a.labelPos||(a.labelPos="right");var o=G("text",{className:"chart-label",x:a.labelPos==="left"?Le:n-ks(r+"",4.5)-Le,y:0,dy:lt/-2+"px","font-size":lt+"px","text-anchor":"start",innerHTML:r+""}),l=G("g",{transform:"translate(0, "+t+")"});return l.appendChild(s),l.appendChild(o),l}function Dc(e,t,n,r){var a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:"",i=arguments.length>5&&arguments[5]!==void 0?arguments[5]:0,s=arguments.length>6&&arguments[6]!==void 0?arguments[6]:0,o=arguments.length>7&&arguments[7]!==void 0?arguments[7]:{},l=Es(t,o.zeroLine),u=$f(l,2),c=u[0],f=u[1];f-=s,c===0&&(c=o.minHeight,f-=o.minHeight),on(e)||(e=0),on(f)||(f=0),on(c,!0)||(c=0),on(n,!0)||(n=0);var d=G("rect",{className:"bar mini",style:"fill: "+r,"data-point-index":i,x:e,y:f,width:n,height:c});if((a+="")||a.length){d.setAttribute("y",0),d.setAttribute("x",0);var h=G("text",{className:"data-point-value",x:n/2,y:0,dy:lt/2*-1+"px","font-size":lt+"px","text-anchor":"middle",innerHTML:a}),p=G("g",{"data-point-index":i,transform:"translate("+e+", "+f+")"});return p.appendChild(d),p.appendChild(h),p}return d}function jc(e,t,n,r){var a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:"",i=arguments.length>5&&arguments[5]!==void 0?arguments[5]:0,s=G("circle",{style:"fill: "+r,"data-point-index":i,cx:e,cy:t,r:n});if((a+="")||a.length){s.setAttribute("cy",0),s.setAttribute("cx",0);var o=G("text",{className:"data-point-value",x:0,y:0,dy:lt/2*-1-n+"px","font-size":lt+"px","text-anchor":"middle",innerHTML:a}),l=G("g",{"data-point-index":i,transform:"translate("+e+", "+t+")"});return l.appendChild(s),l.appendChild(o),l}return s}function Rc(e,t,n){var r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:{},a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:{},i=t.map(function(f,d){return e[d]+","+f}).join("L");r.spline&&(i=Ts(e,t));var s=Lr("M"+i,"line-graph-path",n);if(r.heatline){var o=ai(a.svgDefs,n);s.style.stroke="url(#"+o+")"}var l={path:s};if(r.regionFill){var u=ai(a.svgDefs,n,!0),c="M"+e[0]+","+a.zeroLine+"L"+i+"L"+e.slice(-1)[0]+","+a.zeroLine;l.region=Lr(c,"region-fill","none","url(#"+u+")")}return l}function or(e,t,n,r){var a=typeof t=="string"?t:t.join(", ");return[e,{transform:n.join(", ")},r,Me,"translate",{transform:a}]}function Ic(e,t,n){return or(e,[n,0],[t,0],kn)}function ii(e,t,n){return or(e,[0,n],[0,t],kn)}function Fc(e,t,n,r){var a=t-n,i=e.childNodes[0];return[[i,{height:a,"stroke-dasharray":i.getAttribute("width")+", "+a},kn,Me],or(e,[0,r],[0,n],kn)]}function Hc(e,t,n,r){var a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:0,i=Es(n,(arguments.length>5&&arguments[5]!==void 0?arguments[5]:{}).zeroLine),s=rd(i,2),o=s[0],l=s[1];return l-=a,e.nodeName!=="rect"?[[e.childNodes[0],{width:r,height:o},Gn,Me],or(e,e.getAttribute("transform").split("(")[1].slice(0,-1),[t,l],kn)]:[[e,{width:r,height:o,x:t,y:l},Gn,Me]]}function zc(e,t,n){return e.nodeName!=="circle"?[or(e,e.getAttribute("transform").split("(")[1].slice(0,-1),[t,n],kn)]:[[e,{cx:t,cy:n},Gn,Me]]}function Wc(e,t,n,r,a){var i=[],s=n.map(function(f,d){return t[d]+","+f}).join("L");a&&(s=Ts(t,n));var o=[e.path,{d:"M"+s},di,Me];if(i.push(o),e.region){var l=t[0]+","+r+"L",u="L"+t.slice(-1)[0]+", "+r,c=[e.region,{d:"M"+l+s+u},di,Me];i.push(c)}return i}function si(e,t){return[e,{d:t},Gn,Me]}function Bc(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t3&&arguments[3]!==void 0?arguments[3]:"linear",a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:void 0,i=arguments.length>5&&arguments[5]!==void 0?arguments[5]:{},s=e.cloneNode(!0),o=e.cloneNode(!0);for(var l in t){var u=void 0;u=l==="transform"?document.createElementNS("http://www.w3.org/2000/svg","animateTransform"):document.createElementNS("http://www.w3.org/2000/svg","animate");var c=i[l]||e.getAttribute(l),f=t[l],d={attributeName:l,from:c,to:f,begin:"0s",dur:n/1e3+"s",values:c+";"+f,keySplines:sd[r],keyTimes:"0;1",calcMode:"spline",fill:"freeze"};a&&(d.type=a);for(var h in d)u.setAttribute(h,d[h]);s.appendChild(u),a?o.setAttribute(l,"translate("+f+")"):o.setAttribute(l,f)}return[s,o]}function Mr(e,t){e.style.transform=t,e.style.webkitTransform=t,e.style.msTransform=t,e.style.mozTransform=t,e.style.oTransform=t}function Vc(e,t){var n=[],r=[];t.map(function(i){var s=i[0],o=s.parentNode,l=void 0,u=void 0;i[0]=s;var c=Uc.apply(void 0,Bc(i)),f=id(c,2);l=f[0],u=f[1],n.push(u),r.push([l,o]),o&&o.replaceChild(l,s)});var a=e.cloneNode(!0);return r.map(function(i,s){i[1]&&(i[1].replaceChild(n[s],i[0]),t[s][0]=n[s])}),a}function qc(e,t,n){if(n.length!==0){var r=Vc(t,n);t.parentNode==e&&(e.removeChild(t),e.appendChild(r)),setTimeout(function(){r.parentNode==e&&(e.removeChild(r),e.appendChild(t))},ad)}}function Yc(e,t){var n=document.createElement("a");n.style="display: none";var r=new Blob(t,{type:"image/svg+xml; charset=utf-8"}),a=window.URL.createObjectURL(r);n.href=a,n.download=e,document.body.appendChild(n),n.click(),setTimeout(function(){document.body.removeChild(n),window.URL.revokeObjectURL(a)},300)}function Gc(e){var t=e.cloneNode(!0);t.classList.add("chart-container"),t.setAttribute("xmlns","http://www.w3.org/2000/svg"),t.setAttribute("xmlns:xlink","http://www.w3.org/1999/xlink");var n=Ce.create("style",{innerHTML:od});t.insertBefore(n,t.firstChild);var r=Ce.create("div");return r.appendChild(t),r.innerHTML}function Kc(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function Xc(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function Jc(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||typeof t!="object"&&typeof t!="function"?e:t}function Zc(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function oi(e){var t=new Date(e);return t.setMinutes(t.getMinutes()-t.getTimezoneOffset()),t}function $r(e){var t=e.getDate(),n=e.getMonth()+1;return[e.getFullYear(),(n>9?"":"0")+n,(t>9?"":"0")+t].join("-")}function De(e){return new Date(e.getTime())}function ta(e,t){var n=Os(e);return Math.ceil(Qc(n,t)/Fn)}function Qc(e,t){var n=fd*Rs;return(oi(t)-oi(e))/n}function $c(e,t){return e.getMonth()===t.getMonth()&&e.getFullYear()===t.getFullYear()}function Ps(e){var t=arguments.length>1&&arguments[1]!==void 0&&arguments[1],n=dd[e];return t?n.slice(0,3):n}function li(e,t){return new Date(t,e+1,0)}function Os(e){var t=De(e),n=t.getDay();return n!==0&&rn(t,-1*n),t}function rn(e,t){e.setDate(e.getDate()+t)}function tf(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function lr(e,t,n){var r=Object.keys(hi).filter(function(i){return e.includes(i)}),a=hi[r[0]];return Object.assign(a,{constants:t,getData:n}),new pd(a)}function ef(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0?1:-1;if(!isFinite(e))return{mantissa:4503599627370496*t,exponent:972};e=Math.abs(e);var n=Math.floor(Math.log10(e));return[t*(e/Math.pow(10,n)),n]}function cf(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,n=Math.ceil(e),r=Math.floor(t),a=n-r,i=a,s=1;a>5&&(a%2!=0&&(a=++n-r),i=a/2,s=2),a<=2&&(s=a/(i=4)),a===0&&(i=5,s=1);for(var o=[],l=0;l<=i;l++)o.push(r+s*l);return o}function Ln(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0,n=In(e),r=wd(n,2),a=r[0],i=r[1],s=t?t/Math.pow(10,i):0,o=cf(a=a.toFixed(6),s);return o=o.map(function(l){return l*Math.pow(10,i)})}function ff(e){function t(u,c){for(var f=Ln(u),d=f[1]-f[0],h=0,p=1;h1&&arguments[1]!==void 0&&arguments[1],r=Math.max.apply(Math,xa(e)),a=Math.min.apply(Math,xa(e)),i=[];if(r>=0&&a>=0)In(r)[1],i=n?Ln(r,a):Ln(r);else if(r>0&&a<0){var s=Math.abs(a);r>=s?(In(r)[1],i=t(r,s)):(In(s)[1],i=t(s,r).reverse().map(function(u){return-1*u}))}else if(r<=0&&a<=0){var o=Math.abs(a),l=Math.abs(r);In(o)[1],i=(i=n?Ln(o,l):Ln(o)).reverse().map(function(u){return-1*u})}return i}function df(e){var t=Ls(e);return e.indexOf(0)>=0?e.indexOf(0):e[0]>0?-1*e[0]/t:-1*e[e.length-1]/t+(e.length-1)}function Ls(e){return e[1]-e[0]}function vf(e){return e[e.length-1]-e[0]}function vr(e,t){return xs(t.zeroLine-e*t.scaleMultiplier)}function hf(e,t){var n=arguments.length>2&&arguments[2]!==void 0&&arguments[2],r=t.reduce(function(a,i){return Math.abs(i-e)n?s.slice(0,n):ba(s,n-s.length,0),i.values=s}else i.values=a;i.chartType||(i.chartType=t)}),e.yRegions&&e.yRegions.map(function(i){if(i.end1&&arguments[1]!==void 0?arguments[1]:[],n=!(arguments.length>2&&arguments[2]!==void 0)||arguments[2],r=e/t.length;r<=0&&(r=1);var a=r/qf,i=void 0;if(n){var s=Math.max.apply(Math,wf(t.map(function(o){return o.length})));i=Math.ceil(s/a)}return t.map(function(o,l){return(o+="").length>a&&(n?l%i!=0&&(o=""):o=a-3>0?o.slice(0,a-3)+" ...":o.slice(0,a)+".."),o})}function ui(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&arguments[0]!==void 0?arguments[0]:"line",t=arguments[1],n=arguments[2];return e==="axis-mixed"?(n.type="line",new ka(t,n)):mi[e]?new mi[e](t,n):void console.error("Undefined chart type: "+e)}var Df='.chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .legend-dataset-text{fill:#6c7680;font-weight:600}.graph-svg-tip{position:absolute;z-index:99999;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ol,.graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}';cc(Df);var jf=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};Ce.create=function(e,t){var n=document.createElement(e);for(var r in t){var a=t[r];if(r==="inside")Ce(a).appendChild(n);else if(r==="around"){var i=Ce(a);i.parentNode.insertBefore(n,i),n.appendChild(i)}else r==="styles"?(a===void 0?"undefined":jf(a))==="object"&&Object.keys(a).map(function(s){n.style[s]=a[s]}):r in n?n[r]=a:n.setAttribute(r,a)}return n};var Rf={margins:{top:10,bottom:10,left:20,right:20},paddings:{top:20,bottom:40,left:30,right:10},baseHeight:240,titleHeight:20,legendHeight:30,titleFontSize:12},If=700,Ff=400,Hf=100,zf=.5,Wf=0,Bf=4,ci=4,Uf=20,Ms=2,ea=5,an=10,Vf=2,qf=7,Yf=5,Mn=["light-blue","blue","violet","red","orange","yellow","green","light-green","purple","magenta","light-grey","dark-grey"],Gf=["#ebedf0","#c6e48b","#7bc96f","#239a3b","#196127"],Kf={bar:Mn,line:Mn,pie:Mn,percentage:Mn,heatmap:Gf,donut:Mn},fi=Math.PI/180,Ns=360,Xf=(function(){function e(t,n){for(var r=0;r -
            -
            `}),this.hideTip(),this.title=this.container.querySelector(".title"),this.dataPointList=this.container.querySelector(".data-point-list"),this.parent.addEventListener("mouseleave",function(){t.hideTip()})}},{key:"fill",value:function(){var t=this,n=void 0;this.index&&this.container.setAttribute("data-point-index",this.index),n=this.titleValueFirst?""+this.titleValue+""+this.titleName:this.titleName+""+this.titleValue+"",this.title.innerHTML=n,this.dataPointList.innerHTML="",this.listValues.map(function(r,a){var i=t.colors[a]||"black",s=r.formatted===0||r.formatted?r.formatted:r.value,o=Ce.create("li",{styles:{"border-top":"3px solid "+i},innerHTML:''+(s===0||s?s:"")+` - `+(r.title?r.title:"")});t.dataPointList.appendChild(o)})}},{key:"calcPosition",value:function(){var t=this.container.offsetWidth;this.top=this.y-this.container.offsetHeight-Yf,this.left=this.x-t/2;var n=this.parent.offsetWidth-t,r=this.container.querySelector(".svg-pointer");if(this.left<0)r.style.left="calc(50% - "+-1*this.left+"px)",this.left=0;else if(this.left>n){var a="calc(50% + "+(this.left-n)+"px)";r.style.left=a,this.left=n}else r.style.left="50%"}},{key:"setValues",value:function(t,n){var r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{},a=arguments.length>3&&arguments[3]!==void 0?arguments[3]:[],i=arguments.length>4&&arguments[4]!==void 0?arguments[4]:-1;this.titleName=r.name,this.titleValue=r.value,this.listValues=a,this.x=t,this.y=n,this.titleValueFirst=r.valueFirst||0,this.index=i,this.refresh()}},{key:"hideTip",value:function(){this.container.style.top="0px",this.container.style.left="0px",this.container.style.opacity="0"}},{key:"showTip",value:function(){this.container.style.top=this.top+"px",this.container.style.left=this.left+"px",this.container.style.opacity="1"}}]),e})(),Zf=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Qf={"light-blue":"#7cd6fd",blue:"#5e64ff",violet:"#743ee2",red:"#ff5858",orange:"#ffa00a",yellow:"#feef72",green:"#28a745","light-green":"#98d85b",purple:"#b554ff",magenta:"#ffa3ef",black:"#36114C",grey:"#bdd3e6","light-grey":"#f0f4f7","dark-grey":"#b8c2cc"},Ds=function(e){return/rgb[a]{0,1}\([\d, ]+\)/gim.test(e)?/\D+(\d*)\D+(\d*)\D+(\d*)/gim.exec(e).map(function(t,n){return n!==0?Number(t).toString(16):"#"}).reduce(function(t,n){return""+t+n}):Qf[e]||e},$f=(function(){function e(t,n){var r=[],a=!0,i=!1,s=void 0;try{for(var o,l=t[Symbol.iterator]();!(a=(o=l.next()).done)&&(r.push(o.value),!n||r.length!==n);a=!0);}catch(u){i=!0,s=u}finally{try{!a&&l.return&&l.return()}finally{if(i)throw s}}return r}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}})(),td=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},fn=6,Le=4,js=15,lt=10,Cn="#dadada",Ha="#555b51",ed={bar:function(e){var t=void 0;e.nodeName!=="rect"&&(t=e.getAttribute("transform"),e=e.childNodes[0]);var n=e.cloneNode();return n.style.fill="#000000",n.style.opacity="0.4",t&&n.setAttribute("transform",t),n},dot:function(e){var t=void 0;e.nodeName!=="circle"&&(t=e.getAttribute("transform"),e=e.childNodes[0]);var n=e.cloneNode(),r=e.getAttribute("r"),a=e.getAttribute("fill");return n.setAttribute("r",parseInt(r)+ci),n.setAttribute("fill",a),n.style.opacity="0.6",t&&n.setAttribute("transform",t),n},heat_square:function(e){var t=void 0;e.nodeName!=="circle"&&(t=e.getAttribute("transform"),e=e.childNodes[0]);var n=e.cloneNode(),r=e.getAttribute("r"),a=e.getAttribute("fill");return n.setAttribute("r",parseInt(r)+ci),n.setAttribute("fill",a),n.style.opacity="0.6",t&&n.setAttribute("transform",t),n}},nd={bar:function(e,t){var n=void 0;e.nodeName!=="rect"&&(n=e.getAttribute("transform"),e=e.childNodes[0]);var r=["x","y","width","height"];Object.values(e.attributes).filter(function(a){return r.includes(a.name)&&a.specified}).map(function(a){t.setAttribute(a.name,a.nodeValue)}),n&&t.setAttribute("transform",n)},dot:function(e,t){var n=void 0;e.nodeName!=="circle"&&(n=e.getAttribute("transform"),e=e.childNodes[0]);var r=["cx","cy"];Object.values(e.attributes).filter(function(a){return r.includes(a.name)&&a.specified}).map(function(a){t.setAttribute(a.name,a.nodeValue)}),n&&t.setAttribute("transform",n)},heat_square:function(e,t){var n=void 0;e.nodeName!=="circle"&&(n=e.getAttribute("transform"),e=e.childNodes[0]);var r=["cx","cy"];Object.values(e.attributes).filter(function(a){return r.includes(a.name)&&a.specified}).map(function(a){t.setAttribute(a.name,a.nodeValue)}),n&&t.setAttribute("transform",n)}},rd=(function(){function e(t,n){var r=[],a=!0,i=!1,s=void 0;try{for(var o,l=t[Symbol.iterator]();!(a=(o=l.next()).done)&&(r.push(o.value),!n||r.length!==n);a=!0);}catch(u){i=!0,s=u}finally{try{!a&&l.return&&l.return()}finally{if(i)throw s}}return r}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}})(),Gn=350,di=350,kn=Gn,ad=250,Me="easein",id=(function(){function e(t,n){var r=[],a=!0,i=!1,s=void 0;try{for(var o,l=t[Symbol.iterator]();!(a=(o=l.next()).done)&&(r.push(o.value),!n||r.length!==n);a=!0);}catch(u){i=!0,s=u}finally{try{!a&&l.return&&l.return()}finally{if(i)throw s}}return r}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}})(),sd={ease:"0.25 0.1 0.25 1",linear:"0 0 1 1",easein:"0.1 0.8 0.2 1",easeout:"0 0 0.58 1",easeinout:"0.42 0 0.58 1"},od=".chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto','Oxygen','Ubuntu','Cantarell','Fira Sans','Droid Sans','Helvetica Neue',sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .legend-dataset-text{fill:#6c7680;font-weight:600}.graph-svg-tip{position:absolute;z-index:99999;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ul{padding-left:0;display:flex}.graph-svg-tip ol{padding-left:0;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:' ';border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}",ld=(function(){function e(t,n){for(var r=0;r0&&arguments[0]!==void 0&&arguments[0],r=arguments.length>1&&arguments[1]!==void 0&&arguments[1];n&&fc(this.parent)||(this.updateWidth(),this.calc(n),this.makeChartArea(),this.setupComponents(),this.components.forEach(function(a){return a.setup(t.drawArea)}),this.render(this.components,!1),r&&(this.data=this.realData,setTimeout(function(){t.update(t.data)},this.initTimeout)),this.renderLegend(),this.setupNavigation(r))}},{key:"calc",value:function(){}},{key:"updateWidth",value:function(){this.baseWidth=vc(this.parent),this.width=this.baseWidth-Or(this.measures)}},{key:"makeChartArea",value:function(){this.svg&&this.container.removeChild(this.svg);var t=this.measures;this.svg=bc(this.container,"frappe-chart chart",this.baseWidth,this.baseHeight),this.svgDefs=wc(this.svg),this.title.length&&(this.titleEL=Un("title",t.margins.left,t.margins.top,this.title,{fontSize:t.titleFontSize,fill:"#666666",dy:t.titleFontSize}));var n=Pr(t);this.drawArea=wa(this.type+"-chart chart-draw-area","translate("+xr(t)+", "+n+")"),this.config.showLegend&&(n+=this.height+t.paddings.bottom,this.legendArea=wa("chart-legend","translate("+xr(t)+", "+n+")")),this.title.length&&this.svg.appendChild(this.titleEL),this.svg.appendChild(this.drawArea),this.config.showLegend&&this.svg.appendChild(this.legendArea),this.updateTipOffset(xr(t),Pr(t))}},{key:"updateTipOffset",value:function(t,n){this.tip.offset={x:t,y:n}}},{key:"setupComponents",value:function(){this.components=new Map}},{key:"update",value:function(t){t||console.error("No data to update."),this.data=this.prepareData(t),this.calc(),this.render(this.components,this.config.animate),this.renderLegend()}},{key:"render",value:function(){var t=this,n=arguments.length>0&&arguments[0]!==void 0?arguments[0]:this.components,r=!(arguments.length>1&&arguments[1]!==void 0)||arguments[1];this.config.isNavigable&&this.overlays.map(function(i){return i.parentNode.removeChild(i)});var a=[];n.forEach(function(i){a=a.concat(i.update(r))}),a.length>0?(qc(this.container,this.svg,a),setTimeout(function(){n.forEach(function(i){return i.make()}),t.updateNav()},Ff)):(n.forEach(function(i){return i.make()}),this.updateNav())}},{key:"updateNav",value:function(){this.config.isNavigable&&(this.makeOverlay(),this.bindUnits())}},{key:"renderLegend",value:function(){}},{key:"setupNavigation",value:function(){var t=this,n=arguments.length>0&&arguments[0]!==void 0&&arguments[0];this.config.isNavigable&&n&&(this.bindOverlay(),this.keyActions={13:this.onEnterKey.bind(this),37:this.onLeftArrow.bind(this),38:this.onUpArrow.bind(this),39:this.onRightArrow.bind(this),40:this.onDownArrow.bind(this)},document.addEventListener("keydown",function(r){dc(t.container)&&(r=r||window.event,t.keyActions[r.keyCode]&&t.keyActions[r.keyCode]())}))}},{key:"makeOverlay",value:function(){}},{key:"updateOverlay",value:function(){}},{key:"bindOverlay",value:function(){}},{key:"bindUnits",value:function(){}},{key:"onLeftArrow",value:function(){}},{key:"onRightArrow",value:function(){}},{key:"onUpArrow",value:function(){}},{key:"onDownArrow",value:function(){}},{key:"onEnterKey",value:function(){}},{key:"addDataPoint",value:function(){}},{key:"removeDataPoint",value:function(){}},{key:"getDataPoint",value:function(){}},{key:"setCurrentDataPoint",value:function(){}},{key:"updateDataset",value:function(){}},{key:"export",value:function(){var t=Gc(this.svg);Yc(this.title||"Chart",[t])}}]),e})(),ud=(function(){function e(t,n){for(var r=0;r=0}),s=i;if(i.length>a){i.sort(function(l,u){return u[0]-l[0]}),s=i.slice(0,a-1);var o=0;i.slice(a-1).map(function(l){o+=l[0]}),s.push([o,"Rest"]),this.colors[a-1]="grey"}r.labels=[],s.map(function(l){r.sliceTotals.push(gc(l[0])),r.labels.push(l[1])}),r.grandTotal=r.sliceTotals.reduce(function(l,u){return l+u},0),this.center={x:this.width/2,y:this.height/2}}},{key:"renderLegend",value:function(){var n=this,r=this.state;this.legendArea.textContent="",this.legendTotals=r.sliceTotals.slice(0,this.config.maxLegendPoints);var a=0,i=0;this.legendTotals.map(function(s,o){var l=150,u=Math.floor((n.width-Or(n.measures))/l);n.legendTotals.lengthu&&(a=0,i+=20);var c=l*a+5,f=n.config.truncateLegends?Ia(r.labels[o],l/10):r.labels[o],d=n.config.formatTooltipY?n.config.formatTooltipY(s):s,h=Sc(c,i,5,n.colors[o],f+": "+d,!1);n.legendArea.appendChild(h),a++})}}]),t})(za),vi=12,Fn=7,Rs=1e3,fd=86400,dd=["January","February","March","April","May","June","July","August","September","October","November","December"],vd=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],Tt=(function(){function e(t,n){var r=[],a=!0,i=!1,s=void 0;try{for(var o,l=t[Symbol.iterator]();!(a=(o=l.next()).done)&&(r.push(o.value),!n||r.length!==n);a=!0);}catch(u){i=!0,s=u}finally{try{!a&&l.return&&l.return()}finally{if(i)throw s}}return r}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}})(),hd=(function(){function e(t,n){for(var r=0;r0&&arguments[0]!==void 0)||arguments[0];this.refresh();var n=[];return t&&(n=this.animateElements(this.data)||[]),n}}]),e})(),hi={donutSlices:{layerClass:"donut-slices",makeElements:function(e){return e.sliceStrings.map(function(t,n){var r=Lr(t,"donut-path",e.colors[n],"none",e.strokeWidth);return r.style.transition="transform .3s;",r})},animateElements:function(e){return this.store.map(function(t,n){return si(t,e.sliceStrings[n])})}},pieSlices:{layerClass:"pie-slices",makeElements:function(e){return e.sliceStrings.map(function(t,n){var r=Lr(t,"pie-path","none",e.colors[n]);return r.style.transition="transform .3s;",r})},animateElements:function(e){return this.store.map(function(t,n){return si(t,e.sliceStrings[n])})}},percentageBars:{layerClass:"percentage-bars",makeElements:function(e){var t=this;return e.xPositions.map(function(n,r){return Tc(n,0,e.widths[r],t.constants.barHeight,t.constants.barDepth,e.colors[r])})},animateElements:function(e){if(e)return[]}},yAxis:{layerClass:"y axis",makeElements:function(e){var t=this;return e.positions.map(function(n,r){return Oc(n,e.labels[r],t.constants.width,{mode:t.constants.mode,pos:t.constants.pos,shortenNumbers:t.constants.shortenNumbers})})},animateElements:function(e){var t=e.positions,n=e.labels,r=this.oldData.positions,a=this.oldData.labels,i=Et(r,t),s=Tt(i,2);r=s[0],t=s[1];var o=Et(a,n),l=Tt(o,2);return a=l[0],n=l[1],this.render({positions:r,labels:n}),this.store.map(function(u,c){return ii(u,t[c],r[c])})}},xAxis:{layerClass:"x axis",makeElements:function(e){var t=this;return e.positions.map(function(n,r){return Lc(n,e.calcLabels[r],t.constants.height,{mode:t.constants.mode,pos:t.constants.pos})})},animateElements:function(e){var t=e.positions,n=e.calcLabels,r=this.oldData.positions,a=this.oldData.calcLabels,i=Et(r,t),s=Tt(i,2);r=s[0],t=s[1];var o=Et(a,n),l=Tt(o,2);return a=l[0],n=l[1],this.render({positions:r,calcLabels:n}),this.store.map(function(u,c){return Ic(u,t[c],r[c])})}},yMarkers:{layerClass:"y-markers",makeElements:function(e){var t=this;return e.map(function(n){return Mc(n.position,n.label,t.constants.width,{labelPos:n.options.labelPos,mode:"span",lineType:"dashed"})})},animateElements:function(e){var t=Et(this.oldData,e),n=Tt(t,2);this.oldData=n[0];var r=(e=n[1]).map(function(o){return o.position}),a=e.map(function(o){return o.label}),i=e.map(function(o){return o.options}),s=this.oldData.map(function(o){return o.position});return this.render(s.map(function(o,l){return{position:s[l],label:a[l],options:i[l]}})),this.store.map(function(o,l){return ii(o,r[l],s[l])})}},yRegions:{layerClass:"y-regions",makeElements:function(e){var t=this;return e.map(function(n){return Nc(n.startPos,n.endPos,t.constants.width,n.label,{labelPos:n.options.labelPos})})},animateElements:function(e){var t=Et(this.oldData,e),n=Tt(t,2);this.oldData=n[0];var r=(e=n[1]).map(function(c){return c.endPos}),a=e.map(function(c){return c.label}),i=e.map(function(c){return c.startPos}),s=e.map(function(c){return c.options}),o=this.oldData.map(function(c){return c.endPos}),l=this.oldData.map(function(c){return c.startPos});this.render(o.map(function(c,f){return{startPos:l[f],endPos:o[f],label:a[f],options:s[f]}}));var u=[];return this.store.map(function(c,f){u=u.concat(Fc(c,i[f],r[f],o[f]))}),u}},heatDomain:{layerClass:function(){return"heat-domain domain-"+this.constants.index},makeElements:function(e){var t=this,n=this.constants,r=n.index,a=n.colWidth,i=n.rowHeight,s=n.squareSize,o=n.radius,l=n.xTranslate,u=0;return this.serializedSubDomains=[],e.cols.map(function(c,f){f===1&&t.labels.push(Un("domain-name",l,-12,Ps(r,!0).toUpperCase(),{fontSize:9})),c.map(function(d,h){if(d.fill){var p={"data-date":d.yyyyMmDd,"data-value":d.dataValue,"data-day":h},y=Cs("day",l,u,s,o,d.fill,p);t.serializedSubDomains.push(y)}u+=i}),u=0,l+=a}),this.serializedSubDomains},animateElements:function(e){if(e)return[]}},barGraph:{layerClass:function(){return"dataset-units dataset-bars dataset-"+this.constants.index},makeElements:function(e){var t=this.constants;return this.unitType="bar",this.units=e.yPositions.map(function(n,r){return Dc(e.xPositions[r],n,e.barWidth,t.color,e.labels[r],r,e.offsets[r],{zeroLine:e.zeroLine,barsWidth:e.barsWidth,minHeight:t.minHeight})}),this.units},animateElements:function(e){var t=e.xPositions,n=e.yPositions,r=e.offsets,a=e.labels,i=this.oldData.xPositions,s=this.oldData.yPositions,o=this.oldData.offsets,l=this.oldData.labels,u=Et(i,t),c=Tt(u,2);i=c[0],t=c[1];var f=Et(s,n),d=Tt(f,2);s=d[0],n=d[1];var h=Et(o,r),p=Tt(h,2);o=p[0],r=p[1];var y=Et(l,a),m=Tt(y,2);l=m[0],a=m[1],this.render({xPositions:i,yPositions:s,offsets:o,labels:a,zeroLine:this.oldData.zeroLine,barsWidth:this.oldData.barsWidth,barWidth:this.oldData.barWidth});var _=[];return this.store.map(function(A,x){_=_.concat(Hc(A,t[x],n[x],e.barWidth,r[x],{zeroLine:e.zeroLine}))}),_}},lineGraph:{layerClass:function(){return"dataset-units dataset-line dataset-"+this.constants.index},makeElements:function(e){var t=this.constants;return this.unitType="dot",this.paths={},t.hideLine||(this.paths=Rc(e.xPositions,e.yPositions,t.color,{heatline:t.heatline,regionFill:t.regionFill,spline:t.spline},{svgDefs:t.svgDefs,zeroLine:e.zeroLine})),this.units=[],t.hideDots||(this.units=e.yPositions.map(function(n,r){return jc(e.xPositions[r],n,e.radius,t.color,t.valuesOverPoints?e.values[r]:"",r)})),Object.values(this.paths).concat(this.units)},animateElements:function(e){var t=e.xPositions,n=e.yPositions,r=e.values,a=this.oldData.xPositions,i=this.oldData.yPositions,s=this.oldData.values,o=Et(a,t),l=Tt(o,2);a=l[0],t=l[1];var u=Et(i,n),c=Tt(u,2);i=c[0],n=c[1];var f=Et(s,r),d=Tt(f,2);s=d[0],r=d[1],this.render({xPositions:a,yPositions:i,values:r,zeroLine:this.oldData.zeroLine,radius:this.oldData.radius});var h=[];return Object.keys(this.paths).length&&(h=h.concat(Wc(this.paths,t,n,e.zeroLine,this.constants.spline))),this.units.length&&this.units.map(function(p,y){h=h.concat(zc(p,t[y],n[y]))}),h}}},gd=(function(){function e(t,n){for(var r=0;r0?n.formattedLabels[o]:n.state.labels[o])+": ",h=r.sliceTotals[o]/r.grandTotal;n.tip.setValues(c,f,{name:d,value:(100*h).toFixed(1)+"%"}),n.tip.showTip()}})}}]),t})(Wa),_d=(function(){function e(t,n){for(var r=0;rthis.width?this.center.x:this.center.y;var a=this.radius,i=this.clockWise,s=r.slicesProperties||[];r.sliceStrings=[],r.slicesProperties=[];var o=180-this.config.startAngle;r.sliceTotals.map(function(l,u){var c=o,f=l/r.grandTotal*Ns,d=f>180?1:0,h=i?-f:f,p=o+=h,y=cn(c,a),m=cn(p,a),_=n.init&&s[u],A=void 0,x=void 0;n.init?(A=_?_.startPosition:y,x=_?_.endPosition:y):(A=y,x=m);var E=f===360?kc(A,x,n.center,n.radius,i,d):xc(A,x,n.center,n.radius,i,d);r.sliceStrings.push(E),r.slicesProperties.push({startPosition:y,endPosition:m,value:l,total:r.grandTotal,startAngle:c,endAngle:p,angle:h})}),this.init=0}},{key:"setupComponents",value:function(){var n=this.state,r=[["pieSlices",{},(function(){return{sliceStrings:n.sliceStrings,colors:this.colors}}).bind(this)]];this.components=new Map(r.map(function(a){var i=lr.apply(void 0,sf(a));return[a[0],i]}))}},{key:"calTranslateByAngle",value:function(n){var r=this.radius,a=this.hoverRadio,i=cn(n.startAngle+n.angle/2,r);return"translate3d("+i.x*a+"px,"+i.y*a+"px,0)"}},{key:"hoverSlice",value:function(n,r,a,i){if(n){var s=this.colors[r];if(a){Mr(n,this.calTranslateByAngle(this.state.slicesProperties[r])),n.style.fill=Fa(s,50);var o=Yn(this.svg),l=i.pageX-o.left+10,u=i.pageY-o.top-10,c=(this.formatted_labels&&this.formatted_labels.length>0?this.formatted_labels[r]:this.state.labels[r])+": ",f=(100*this.state.sliceTotals[r]/this.state.grandTotal).toFixed(1);this.tip.setValues(l,u,{name:c,value:f+"%"}),this.tip.showTip()}else Mr(n,"translate3d(0,0,0)"),this.tip.hideTip(),n.style.fill=s}}},{key:"bindTooltip",value:function(){this.container.addEventListener("mousemove",this.mouseMove),this.container.addEventListener("mouseleave",this.mouseLeave)}},{key:"mouseMove",value:function(n){var r=n.target,a=this.components.get("pieSlices").store,i=this.curActiveSliceIndex,s=this.curActiveSlice;if(a.includes(r)){var o=a.indexOf(r);this.hoverSlice(s,i,!1),this.curActiveSlice=r,this.curActiveSliceIndex=o,this.hoverSlice(r,o,!0,n)}else this.mouseLeave()}},{key:"mouseLeave",value:function(){this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,!1)}}]),t})(Wa),wd=(function(){function e(t,n){var r=[],a=!0,i=!1,s=void 0;try{for(var o,l=t[Symbol.iterator]();!(a=(o=l.next()).done)&&(r.push(o.value),!n||r.length!==n);a=!0);}catch(u){i=!0,s=u}finally{try{!a&&l.return&&l.return()}finally{if(i)throw s}}return r}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}})(),xd=(function(){function e(t,n){for(var r=0;r0&&arguments[0]!==void 0?arguments[0]:this.data;if(n.start&&n.end&&n.start>n.end)throw new Error("Start date cannot be greater than end date.");if(n.start||(n.start=new Date,n.start.setFullYear(n.start.getFullYear()-1)),n.end||(n.end=new Date),n.dataPoints=n.dataPoints||{},parseInt(Object.keys(n.dataPoints)[0])>1e5){var r={};Object.keys(n.dataPoints).forEach(function(a){var i=new Date(a*Rs);r[$r(i)]=n.dataPoints[a]}),n.dataPoints=r}return n}},{key:"calc",value:function(){var n=this.state;n.start=De(this.data.start),n.end=De(this.data.end),n.firstWeekStart=De(n.start),n.noOfWeeks=ta(n.start,n.end),n.distribution=pf(Object.values(this.data.dataPoints),ea),n.domainConfigs=this.getDomains()}},{key:"setupComponents",value:function(){var n=this,r=this.state,a=this.discreteDomains?0:1,i=r.domainConfigs.map(function(o,l){return["heatDomain",{index:o.index,colWidth:Yt,rowHeight:en,squareSize:an,radius:n.rawChartArgs.radius||0,xTranslate:r.domainConfigs.filter(function(u,c){return c1&&arguments[1]!==void 0?arguments[1]:"",a=[n.getMonth(),n.getFullYear()],i=a[0],s=a[1],o=Os(n),l={index:i,cols:[]};rn(r=De(r)||li(i,s),1);for(var u=ta(o,r),c=[],f=void 0,d=0;d2&&arguments[2]!==void 0&&arguments[2],i=this.state,s=De(n),o=[],l=0;l=i.start&&s<=i.end;a||s.getMonth()!==r||!c?u.yyyyMmDd=$r(s):u=this.getSubDomainConfig(s),o.push(u)}return o}},{key:"getSubDomainConfig",value:function(n){var r=$r(n),a=this.data.dataPoints[r];return{yyyyMmDd:r,dataValue:a||0,fill:this.colors[gf(a,this.state.distribution)]}}}]),t})(za),Ad=(function(){function e(t,n){for(var r=0;r0&&arguments[0]!==void 0?arguments[0]:this.data,this.type)}},{key:"prepareFirstData",value:function(){return kf(arguments.length>0&&arguments[0]!==void 0?arguments[0]:this.data)}},{key:"calc",value:function(){var n=arguments.length>0&&arguments[0]!==void 0&&arguments[0];this.calcXPositions(),n||this.calcYAxisParameters(this.getAllYValues(),this.type==="line"),this.makeDataByIndex()}},{key:"calcXPositions",value:function(){var n=this.state,r=this.data.labels;n.datasetLength=r.length,n.unitWidth=this.width/n.datasetLength,n.xOffset=n.unitWidth/2,n.xAxis={labels:r,positions:r.map(function(a,i){return xs(n.xOffset+i*n.unitWidth)})}}},{key:"calcYAxisParameters",value:function(n){var r=ff(n,arguments.length>1&&arguments[1]!==void 0?arguments[1]:"false"),a=this.height/vf(r),i=Ls(r)*a,s=this.height-df(r)*i;this.state.yAxis={labels:r,positions:r.map(function(o){return s-o*a}),scaleMultiplier:a,zeroLine:s},this.calcDatasetPoints(),this.calcYExtremes(),this.calcYRegions()}},{key:"calcDatasetPoints",value:function(){var n=this.state,r=function(a){return a.map(function(i){return vr(i,n.yAxis)})};n.datasets=this.data.datasets.map(function(a,i){var s=a.values,o=a.cumulativeYs||[];return{name:a.name&&a.name.replace(/<|>|&/g,function(l){return l=="&"?"&":l=="<"?"<":">"}),index:i,chartType:a.chartType,values:s,yPositions:r(s),cumulativeYs:o,cumulativeYPos:r(o)}})}},{key:"calcYExtremes",value:function(){var n=this.state;if(this.barOptions.stacked)return void(n.yExtremes=n.datasets[n.datasets.length-1].cumulativeYPos);n.yExtremes=new Array(n.datasetLength).fill(9999),n.datasets.map(function(r){r.yPositions.map(function(a,i){aPr(a)?n.mapTooltipXPosition(s):n.tip.hideTip()})}},{key:"mapTooltipXPosition",value:function(n){var r=this.state;if(r.yExtremes){var a=hf(n,r.xAxis.positions,!0);if(a>=0){var i=this.dataByIndex[a];this.tip.setValues(i.xPos+this.tip.offset.x,i.yExtreme+this.tip.offset.y,{name:i.formattedLabel,value:""},i.values,a),this.tip.showTip()}}}},{key:"renderLegend",value:function(){var n=this,r=this.data;r.datasets.length>1&&(this.legendArea.textContent="",r.datasets.map(function(a,i){var s=Hf,o=Cc(s*i,"0",s,n.colors[i],a.name,n.config.truncateLegends);n.legendArea.appendChild(o)}))}},{key:"makeOverlay",value:function(){var n=this;if(this.init)return void(this.init=0);this.overlayGuides&&this.overlayGuides.forEach(function(r){var a=r.overlay;a.parentNode.removeChild(a)}),this.overlayGuides=this.dataUnitComponents.map(function(r){return{type:r.unitType,overlay:void 0,units:r.units}}),this.state.currentIndex===void 0&&(this.state.currentIndex=this.state.datasetLength-1),this.overlayGuides.map(function(r){var a=r.units[n.state.currentIndex];r.overlay=ed[r.type](a),n.drawArea.appendChild(r.overlay)})}},{key:"updateOverlayGuides",value:function(){this.overlayGuides&&this.overlayGuides.forEach(function(n){var r=n.overlay;r.parentNode.removeChild(r)})}},{key:"bindOverlay",value:function(){var n=this;this.parent.addEventListener("data-select",function(){n.updateOverlay()})}},{key:"bindUnits",value:function(){var n=this;this.dataUnitComponents.map(function(r){r.units.map(function(a){a.addEventListener("click",function(){var i=a.getAttribute("data-point-index");n.setCurrentDataPoint(i)})})}),this.tip.container.addEventListener("click",function(){var r=n.tip.container.getAttribute("data-point-index");n.setCurrentDataPoint(r)})}},{key:"updateOverlay",value:function(){var n=this;this.overlayGuides.map(function(r){var a=r.units[n.state.currentIndex];nd[r.type](a,r.overlay)})}},{key:"onLeftArrow",value:function(){this.setCurrentDataPoint(this.state.currentIndex-1)}},{key:"onRightArrow",value:function(){this.setCurrentDataPoint(this.state.currentIndex+1)}},{key:"getDataPoint",value:function(){var n=arguments.length>0&&arguments[0]!==void 0?arguments[0]:this.state.currentIndex,r=this.state;return{index:n,label:r.xAxis.labels[n],values:r.datasets.map(function(a){return a.values[n]})}}},{key:"setCurrentDataPoint",value:function(n){var r=this.state;(n=parseInt(n))<0&&(n=0),n>=r.xAxis.labels.length&&(n=r.xAxis.labels.length-1),n!==r.currentIndex&&(r.currentIndex=n,hc(this.parent,"data-select",this.getDataPoint()))}},{key:"addDataPoint",value:function(n,r){var a=arguments.length>2&&arguments[2]!==void 0?arguments[2]:this.state.datasetLength;na(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"addDataPoint",this).call(this,n,r,a),this.data.labels.splice(a,0,n),this.data.datasets.map(function(i,s){i.values.splice(a,0,r[s])}),this.update(this.data)}},{key:"removeDataPoint",value:function(){var n=arguments.length>0&&arguments[0]!==void 0?arguments[0]:this.state.datasetLength-1;this.data.labels.length<=1||(na(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"removeDataPoint",this).call(this,n),this.data.labels.splice(n,1),this.data.datasets.map(function(r){r.values.splice(n,1)}),this.update(this.data))}},{key:"updateDataset",value:function(n){var r=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0;this.data.datasets[r].values=n,this.update(this.data)}},{key:"updateDatasets",value:function(n){this.data.datasets.map(function(r,a){n[a]&&(r.values=n[a])}),this.update(this.data)}}]),t})(za),Ed=(function(){function e(t,n){for(var r=0;rthis.width?this.center.x-this.strokeWidth/2:this.center.y-this.strokeWidth/2;var a=this.radius,i=this.clockWise,s=r.slicesProperties||[];r.sliceStrings=[],r.slicesProperties=[];var o=180-this.config.startAngle;r.sliceTotals.map(function(l,u){var c=o,f=l/r.grandTotal*Ns,d=f>180?1:0,h=i?-f:f,p=o+=h,y=cn(c,a),m=cn(p,a),_=n.init&&s[u],A=void 0,x=void 0;n.init?(A=_?_.startPosition:y,x=_?_.endPosition:y):(A=y,x=m);var E=f===360?Ec(A,x,n.center,n.radius,n.clockWise,d):Ac(A,x,n.center,n.radius,n.clockWise,d);r.sliceStrings.push(E),r.slicesProperties.push({startPosition:y,endPosition:m,value:l,total:r.grandTotal,startAngle:c,endAngle:p,angle:h})}),this.init=0}},{key:"setupComponents",value:function(){var n=this.state,r=[["donutSlices",{},(function(){return{sliceStrings:n.sliceStrings,colors:this.colors,strokeWidth:this.strokeWidth}}).bind(this)]];this.components=new Map(r.map(function(a){var i=lr.apply(void 0,Sf(a));return[a[0],i]}))}},{key:"calTranslateByAngle",value:function(n){var r=this.radius,a=this.hoverRadio,i=cn(n.startAngle+n.angle/2,r);return"translate3d("+i.x*a+"px,"+i.y*a+"px,0)"}},{key:"hoverSlice",value:function(n,r,a,i){if(n){var s=this.colors[r];if(a){Mr(n,this.calTranslateByAngle(this.state.slicesProperties[r])),n.style.stroke=Fa(s,50);var o=Yn(this.svg),l=i.pageX-o.left+10,u=i.pageY-o.top-10,c=(this.formatted_labels&&this.formatted_labels.length>0?this.formatted_labels[r]:this.state.labels[r])+": ",f=(100*this.state.sliceTotals[r]/this.state.grandTotal).toFixed(1);this.tip.setValues(l,u,{name:c,value:f+"%"}),this.tip.showTip()}else Mr(n,"translate3d(0,0,0)"),this.tip.hideTip(),n.style.stroke=s}}},{key:"bindTooltip",value:function(){this.container.addEventListener("mousemove",this.mouseMove),this.container.addEventListener("mouseleave",this.mouseLeave)}},{key:"mouseMove",value:function(n){var r=n.target,a=this.components.get("donutSlices").store,i=this.curActiveSliceIndex,s=this.curActiveSlice;if(a.includes(r)){var o=a.indexOf(r);this.hoverSlice(s,i,!1),this.curActiveSlice=r,this.curActiveSliceIndex=o,this.hoverSlice(r,o,!0,n)}else this.mouseLeave()}},{key:"mouseLeave",value:function(){this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,!1)}}]),t})(Wa),mi={bar:ka,line:ka,percentage:yd,heatmap:kd,pie:bd,donut:Td},Cd=function e(t,n){return Mf(this,e),Nf(n.type,t,n)};let yi=!1;function Sd(){yi||typeof window>"u"||(yi=!0,window.addEventListener("error",e=>{const t=e.message??"";t.includes("removeChild")&&t.includes("not a child")&&(e.preventDefault(),e.stopImmediatePropagation())},!0))}var Pd=C('

            ');function Nn(e,t){K(t,!0),Sd();let n=kt(t,"valuesOverPoints",3,!1),r=N(null);function a(f,d){return getComputedStyle(document.documentElement).getPropertyValue(f).trim()||d}function i(){return[a("--accent","#eaa45d"),a("--chart-2","#6aa3d8"),a("--chart-3","#7fb77e"),a("--chart-4","#d88c8c"),a("--chart-5","#b39ddb"),a("--chart-6","#e0a458")]}let s=null;Pe(()=>{if(Ke.current,!v(r))return;const f=v(r),d=he(()=>(f.innerHTML="",new Cd(f,{title:t.title,data:t.data,type:t.type,height:t.type==="bar"?240:220,colors:i(),valuesOverPoints:n()?1:0})));return s=d,()=>{s=null;try{d.destroy()}catch{}}}),Pe(()=>{const f=t.data;s&&s.update(f)});var o=Pd(),l=b(o),u=b(l),c=k(l,2);zr(c,f=>S(r,f),()=>v(r)),F(()=>j(u,t.title)),w(e,o),X()}var Od=C('
            Total calls
            Total input tokens
            Total output tokens
            Total tokens
            ');function Ld(e,t){K(t,!0);const n=J(()=>Object.values(t.stats).reduce((_,A)=>({calls:_.calls+A.num_times_called,input:_.input+A.input_tokens,output:_.output+A.output_tokens}),{calls:0,input:0,output:0}));var r=Od(),a=b(r),i=b(a),s=k(b(i)),o=b(s),l=k(i),u=k(b(l)),c=b(u),f=k(l),d=k(b(f)),h=b(d),p=k(f),y=k(b(p)),m=b(y);F(()=>{j(o,v(n).calls),j(c,v(n).input),j(h,v(n).output),j(m,v(n).input+v(n).output)}),w(e,r),X()}var Md=C('
            ',1),Nd=C('
            No tool stats collected yet.
            '),Dd=C('
            ',1);function jd(e,t){K(t,!0);const n=J(()=>Object.keys(qt.stats).length>0);ge(()=>{qt.refresh()});var r=Dd(),a=Y(r),i=b(a);At(i,{onclick:()=>qt.refresh(),children:(c,f)=>{var d=it("Refresh Stats");w(c,d)},$$slots:{default:!0}});var s=k(i,2);At(s,{onclick:()=>qt.clear(),children:(c,f)=>{var d=it("Clear Stats");w(c,d)},$$slots:{default:!0}});var o=k(a,2);{var l=c=>{var f=Md(),d=Y(f);Ld(d,{get stats(){return qt.stats}});var h=k(d,2),p=b(h),y=k(h,2),m=b(y);{let P=J(()=>Jr(qt.stats,"num_times_called"));Nn(m,{title:"Tool Calls",type:"pie",get data(){return v(P)}})}var _=k(m,2);{let P=J(()=>Jr(qt.stats,"input_tokens"));Nn(_,{title:"Input Tokens",type:"pie",get data(){return v(P)}})}var A=k(_,2);{let P=J(()=>Jr(qt.stats,"output_tokens"));Nn(A,{title:"Output Tokens",type:"pie",get data(){return v(P)}})}var x=k(y,2),E=b(x);{let P=J(()=>ni(qt.stats,"input_tokens","Input Tokens"));Nn(E,{title:"Input Tokens (by tool)",type:"bar",valuesOverPoints:!0,get data(){return v(P)}})}var O=k(E,2);{let P=J(()=>ni(qt.stats,"output_tokens","Output Tokens"));Nn(O,{title:"Output Tokens (by tool)",type:"bar",valuesOverPoints:!0,get data(){return v(P)}})}F(()=>j(p,`Token estimator: ${qt.estimator??""}`)),w(c,f)},u=c=>{var f=Nd();w(c,f)};W(o,c=>{v(n)?c(l):c(u,-1)})}w(e,r),X()}function Rd(){let e=N(null);return{get active(){return v(e)},open(t){S(e,t,!0)},close(){S(e,null)}}}const Kt=Rd();var Id=C("

            "),Fd=C(''),Hd=C('');function ur(e,t){K(t,!0);let n=kt(t,"open",3,!1),r=kt(t,"title",3,""),a=kt(t,"error",3,""),i=N(null),s=null;Pe(()=>{n()&&v(i)&&(s=document.activeElement,v(i).focus())}),ms(()=>s==null?void 0:s.focus());function o(f){if(f.key==="Escape"){t.onclose();return}if(f.key!=="Tab"||!n()||!v(i))return;const d=v(i).querySelectorAll('a[href], button:not([disabled]), input, textarea, select, [tabindex]:not([tabindex="-1"])');if(d.length===0)return;const h=d[0],p=d[d.length-1],y=document.activeElement;f.shiftKey&&y===h?(f.preventDefault(),p.focus()):!f.shiftKey&&y===p&&(f.preventDefault(),h.focus())}var l=pt();ps("keydown",pa,o);var u=Y(l);{var c=f=>{var d=Hd(),h=b(d),p=b(h),y=k(p,2);{var m=E=>{var O=Id(),P=b(O);F(()=>j(P,r())),w(E,O)};W(y,E=>{r()&&E(m)})}var _=k(y,2);{var A=E=>{var O=Fd(),P=b(O);F(()=>j(P,a())),w(E,O)};W(_,E=>{a()&&E(A)})}var x=k(_,2);ir(x,()=>t.children),zr(h,E=>S(i,E),()=>v(i)),Z("click",d,function(...E){var O;(O=t.onclose)==null||O.apply(this,E)}),Z("keydown",d,o),Z("click",h,E=>E.stopPropagation()),Z("keydown",h,E=>{o(E),E.stopPropagation()}),Z("click",p,function(...E){var O;(O=t.onclose)==null||O.apply(this,E)}),w(f,d)};W(u,f=>{n()&&f(c)})}w(e,l),X()}te(["click","keydown"]);function An(){let e=N(!1),t=N("");return{get busy(){return v(e)},get error(){return v(t)},clearError(){S(t,"")},async run(n,r){S(e,!0),S(t,"");const a=await n();return S(e,!1),a.ok?(r(),a):(S(t,a.message??"",!0),a)}}}var zd=C(' ',1);function Wr(e,t){K(t,!0);let n=kt(t,"title",3,""),r=kt(t,"confirmLabel",3,"OK"),a=kt(t,"variant",3,"primary");const i=An();ur(e,{open:!0,get title(){return n()},get error(){return i.error},get onclose(){return t.onclose},children:(s,o)=>{var l=zd(),u=Y(l),c=b(u);ir(c,()=>t.children);var f=k(u,2),d=b(f);At(d,{variant:"secondary",get onclick(){return t.onclose},children:(p,y)=>{var m=it("Cancel");w(p,m)},$$slots:{default:!0}});var h=k(d,2);At(h,{get variant(){return a()},get disabled(){return i.busy},onclick:()=>i.run(t.onconfirm,t.onclose),children:(p,y)=>{var m=pt(),_=Y(m);{var A=E=>{sr(E)},x=E=>{var O=it();F(()=>j(O,r())),w(E,O)};W(_,E=>{i.busy?E(A):E(x,-1)})}w(p,m)},$$slots:{default:!0}}),w(s,l)},$$slots:{default:!0}}),X()}function Wd(e,t){K(t,!0);async function n(){const r=await pe(()=>Ul());return r.ok&&setTimeout(()=>window.close(),1e3),r}Wr(e,{title:"Shutdown Server",confirmLabel:"Shutdown",variant:"danger",onconfirm:n,get onclose(){return t.onclose},children:(r,a)=>{var i=it("Shut down the Serena server?");w(r,i)},$$slots:{default:!0}}),X()}function Bd(e,t){K(t,!0);function n(){ie.clearCancelError(),t.onclose()}Wr(e,{onconfirm:()=>ie.cancel(t.execution),onclose:n,children:(r,a)=>{var i=it(`Are you sure? The execution will continue running until timeout, it will simply no longer be in - the queue. Abandoning a running execution is only advised as a measure for unblocking Serena.`);w(r,i)},$$slots:{default:!0}}),X()}var Ud=C('
          • '),Vd=C('
              '),qd=C('
              No options available
              '),Yd=C('
              ');function Gd(e,t){K(t,!0);let n=kt(t,"value",3,""),r=kt(t,"placeholder",3,"Type to filter…"),a=N(vt(n())),i=N(!1),s=N(null);const o=J(()=>t.options.filter(y=>y.toLowerCase().includes(v(a).toLowerCase())));function l(y){S(a,y,!0),S(i,!1),t.onselect(y)}function u(y){var _;const m=y.relatedTarget;m&&((_=v(s))!=null&&_.contains(m))||S(i,!1)}function c(y,m){(y.key==="Enter"||y.key===" ")&&(y.preventDefault(),l(m))}var f=Yd(),d=b(f),h=k(d,4);{var p=y=>{var m=pt(),_=Y(m);{var A=E=>{var O=Vd();Ht(O,20,()=>v(o),P=>P,(P,T)=>{var M=Ud(),B=b(M);F(()=>{Lt(M,"aria-selected",T===v(a)),j(B,T)}),Z("click",M,()=>l(T)),Z("keydown",M,$=>c($,T)),w(P,M)}),w(E,O)},x=E=>{var O=qd();w(E,O)};W(_,E=>{v(o).length?E(A):E(x,-1)})}w(y,m)};W(h,y=>{v(i)&&y(p)})}zr(f,y=>S(s,y),()=>v(s)),F(()=>Lt(d,"placeholder",r())),Z("focusout",f,u),ps("focus",d,()=>S(i,!0)),Z("input",d,()=>S(i,!0)),qn(d,()=>v(a),y=>S(a,y)),w(e,f),X()}te(["focusout","input","click","keydown"]);var Kd=C(` `,1);function Xd(e,t){K(t,!0);let n=N(vt([])),r=N("");const a=An();ge(()=>{(async()=>S(n,(await Vl()).languages,!0))()});function i(){v(r)&&a.run(()=>pe(()=>ql(v(r))),t.onclose)}ur(e,{open:!0,title:"Add Language",get error(){return a.error},get onclose(){return t.onclose},children:(s,o)=>{var l=Kd(),u=Y(l),c=k(b(u)),f=b(c),d=k(u,4);Gd(d,{get options(){return v(n)},get value(){return v(r)},onselect:m=>S(r,m,!0)});var h=k(d,2),p=b(h);At(p,{variant:"secondary",get onclick(){return t.onclose},children:(m,_)=>{var A=it("Cancel");w(m,A)},$$slots:{default:!0}});var y=k(p,2);{let m=J(()=>a.busy||!v(r));At(y,{get disabled(){return v(m)},onclick:i,children:(_,A)=>{var x=pt(),E=Y(x);{var O=T=>{sr(T)},P=T=>{var M=it("Add Language");w(T,M)};W(E,T=>{a.busy?T(O):T(P,-1)})}w(_,x)},$$slots:{default:!0}})}F(()=>j(f,t.projectName)),w(s,l)},$$slots:{default:!0}}),X()}var Jd=C("Remove language from configuration?",1);function Zd(e,t){K(t,!0),Wr(e,{onconfirm:()=>pe(()=>Yl(t.language)),get onclose(){return t.onclose},children:(n,r)=>{var a=Jd(),i=k(Y(a)),s=b(i);F(()=>j(s,t.language)),w(n,a)},$$slots:{default:!0}}),X()}function Is(e){return/^[A-Za-z0-9_]+(\/[A-Za-z0-9_]+)*$/.test(e)}function Fs(e){return!e||window.confirm("You have unsaved changes. Discard them?")}var Qd=C(' ',1),$d=C(' ',1),tv=C(' ',1);function ev(e,t){K(t,!0);let n=N(vt(he(()=>t.name))),r=N(""),a=N(""),i=N(!1),s=N(vt(he(()=>t.name)));const o=J(()=>v(r)!==v(a)),l=An(),u=An();ge(()=>{(async()=>{const h=(await Gl(t.name)).content??"";S(r,h,!0),S(a,h,!0)})()});function c(){Fs(v(o))&&t.onclose()}function f(){l.run(()=>pe(()=>_s(v(n),v(r))),t.onclose)}function d(){if(!Is(v(s))||v(s)===v(n)){S(i,!1);return}u.run(()=>pe(()=>Xl(v(n),v(s))),()=>{S(n,v(s),!0),S(i,!1)})}{let h=J(()=>l.error||u.error);ur(e,{open:!0,get error(){return v(h)},onclose:c,children:(p,y)=>{var m=tv(),_=Y(m),A=k(b(_));{var x=B=>{var $=Qd(),ct=Y($),I=k(ct,2);F(()=>I.disabled=u.busy),Z("keydown",ct,V=>V.key==="Enter"&&d()),qn(ct,()=>v(s),V=>S(s,V)),Z("click",I,d),w(B,$)},E=B=>{var $=$d(),ct=Y($),I=b(ct),V=k(ct,2);F(()=>j(I,v(n))),Z("click",V,()=>{S(i,!0),S(s,v(n),!0)}),w(B,$)};W(A,B=>{v(i)?B(x):B(E,-1)})}var O=k(_,2),P=k(O,2),T=b(P);At(T,{variant:"secondary",onclick:c,children:(B,$)=>{var ct=it("Cancel");w(B,ct)},$$slots:{default:!0}});var M=k(T,2);At(M,{get disabled(){return l.busy},onclick:f,children:(B,$)=>{var ct=it("Save");w(B,ct)},$$slots:{default:!0}}),qn(O,()=>v(r),B=>S(r,B)),w(p,m)},$$slots:{default:!0}})}X()}te(["keydown","click"]);var nv=C(` `,1);function rv(e,t){K(t,!0);let n=N("");const r=J(()=>Is(v(n))),a=An();function i(){v(r)&&a.run(()=>pe(()=>_s(v(n),"")),()=>t.oncreated(v(n)))}ur(e,{open:!0,get error(){return a.error},get onclose(){return t.onclose},children:(s,o)=>{var l=nv(),u=Y(l),c=k(b(u)),f=b(c),d=k(u,4),h=k(d,2),p=b(h);At(p,{variant:"secondary",get onclick(){return t.onclose},children:(m,_)=>{var A=it("Cancel");w(m,A)},$$slots:{default:!0}});var y=k(p,2);{let m=J(()=>!v(r)||a.busy);At(y,{get disabled(){return v(m)},onclick:i,children:(_,A)=>{var x=pt(),E=Y(x);{var O=T=>{sr(T)},P=T=>{var M=it("Create");w(T,M)};W(E,T=>{a.busy?T(O):T(P,-1)})}w(_,x)},$$slots:{default:!0}})}F(()=>j(f,t.projectName)),qn(d,()=>v(n),m=>S(n,m)),w(s,l)},$$slots:{default:!0}}),X()}var av=C("Delete memory ?",1);function iv(e,t){K(t,!0),Wr(e,{variant:"danger",onconfirm:()=>pe(()=>Kl(t.name)),get onclose(){return t.onclose},children:(n,r)=>{var a=av(),i=k(Y(a)),s=b(i);F(()=>j(s,t.name)),w(n,a)},$$slots:{default:!0}}),X()}var sv=C(' ',1);function ov(e,t){K(t,!0);let n=N(""),r=N("");const a=J(()=>v(n)!==v(r)),i=An();async function s(){const u=(await Jl()).content;S(n,u,!0),S(r,u,!0)}ge(()=>{s()});function o(){Fs(v(a))&&t.onclose()}function l(){i.run(()=>pe(()=>Zl(v(n))),t.onclose)}ur(e,{open:!0,title:"Global Serena Configuration",get error(){return i.error},onclose:o,children:(u,c)=>{var f=sv(),d=k(Y(f),2),h=k(d,2),p=b(h);At(p,{variant:"secondary",onclick:o,children:(m,_)=>{var A=it("Cancel");w(m,A)},$$slots:{default:!0}});var y=k(p,2);At(y,{get disabled(){return i.busy},onclick:l,children:(m,_)=>{var A=it("Save");w(m,A)},$$slots:{default:!0}}),qn(d,()=>v(n),m=>S(n,m)),w(u,f)},$$slots:{default:!0}}),X()}function lv(e,t){K(t,!0);const n=J(()=>{var l,u;return String(((u=(l=Cr.data)==null?void 0:l.active_project)==null?void 0:u.name)??"")}),r=J(()=>Kt.active),a=()=>Kt.close();var i=pt(),s=Y(i);{var o=l=>{var u=pt(),c=Y(u);{var f=x=>{Wd(x,{onclose:a})},d=x=>{Bd(x,{get execution(){return v(r).execution},onclose:a})},h=x=>{Xd(x,{get projectName(){return v(n)},onclose:a})},p=x=>{Zd(x,{get language(){return v(r).language},onclose:a})},y=x=>{var E=pt(),O=Y(E);ll(O,()=>v(r).name,P=>{ev(P,{get name(){return v(r).name},onclose:a})}),w(x,E)},m=x=>{rv(x,{get projectName(){return v(n)},onclose:a,oncreated:E=>Kt.open({kind:"editMemory",name:E})})},_=x=>{iv(x,{get name(){return v(r).name},onclose:a})},A=x=>{ov(x,{onclose:a})};W(c,x=>{v(r).kind==="shutdown"?x(f):v(r).kind==="cancelExecution"?x(d,1):v(r).kind==="addLanguage"?x(h,2):v(r).kind==="removeLanguage"?x(p,3):v(r).kind==="editMemory"?x(y,4):v(r).kind==="createMemory"?x(m,5):v(r).kind==="deleteMemory"?x(_,6):v(r).kind==="editSerenaConfig"&&x(A,7)})}w(l,u)};W(s,l=>{v(r)&&l(o)})}w(e,i),X()}var uv=C('
              '),cv=C('
              '),fv=C('
              '),dv=C('
              ',1);function vv(e,t){K(t,!0);let n=N("overview");Pe(()=>{var P,T;document.title=su((T=(P=Cr.data)==null?void 0:P.active_project)==null?void 0:T.name)});const r=P=>P.catch(T=>console.debug("poll failed",T)),a=dr(()=>r(Cr.poll()),1e3),i=dr(()=>r(ie.pollQueued()),1e3),s=dr(()=>r(ie.pollLast()),1e3),o=dr(()=>r(wr.poll()),1e3),l={config:a,queued:i,last:s,logs:o};function u(P){for(const T of Object.values(l))T.stop();for(const T of Nl(P))l[T].start()}function c(P){S(n,P,!0),u(P)}ge(()=>{Ke.init(),u("overview")});var f=dv(),d=Y(f),h=b(d);Ml(h,{get active(){return v(n)},onnavigate:c,onshutdown:()=>Kt.open({kind:"shutdown"})});var p=k(h,2),y=b(p);{var m=P=>{var T=uv(),M=b(T);Qu(M,{onaddlanguage:()=>Kt.open({kind:"addLanguage"}),onremovelanguage:B=>Kt.open({kind:"removeLanguage",language:B}),oneditconfig:()=>Kt.open({kind:"editSerenaConfig"}),onopenmemory:B=>Kt.open({kind:"editMemory",name:B}),oncreatememory:()=>Kt.open({kind:"createMemory"}),ondeletememory:B=>Kt.open({kind:"deleteMemory",name:B}),oncancelexecution:B=>B.is_running?Kt.open({kind:"cancelExecution",execution:B}):void ie.cancel(B)}),w(P,T)};W(y,P=>{v(n)==="overview"&&P(m)})}var _=k(y,2);{var A=P=>{var T=cv(),M=b(T);lc(M,{}),w(P,T)};W(_,P=>{v(n)==="logs"&&P(A)})}var x=k(_,2);{var E=P=>{var T=fv(),M=b(T);jd(M,{}),w(P,T)};W(x,P=>{v(n)==="stats"&&P(E)})}var O=k(d,2);lv(O,{}),w(e,f),X()}al(vv,{target:document.getElementById("app")}); diff --git a/src/serena/resources/dashboard/index.html b/src/serena/resources/dashboard/index.html index ffbd4dd2b..c941e9c86 100644 --- a/src/serena/resources/dashboard/index.html +++ b/src/serena/resources/dashboard/index.html @@ -13,8 +13,8 @@ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" /> - - + +
              From cffa4e8c342d312f6722e0f56626838a0f799eb2 Mon Sep 17 00:00:00 2001 From: Paul Basanets Date: Sat, 30 May 2026 00:33:08 +0300 Subject: [PATCH 3/8] feat(dashboard): port analytics, timeline, stats & Code tab from legacy branch Port the legacy-branch feature set and its follow-on changes. - analytics: ToolCallRecord ring buffer, Entry timing/error/last_called_at fields, 8KB truncation helper, cursor-based reads. - Instrument agent tool dispatch (timing + error capture) and task-executor TaskInfo timing with a race fix around future resolution. - Backend endpoints: timeline, totals, queued-exec timing; /code/* routes (list_dir, file_symbols, workspace_symbol_search, diagnostics_summary). - Frontend: Timeline, SummaryCards, FilterDropdown, Stats charts (sort selector, duration, rate, drilldown), and the Code tab (store, FileTree, Symbols, Search). Co-Authored-By: Claude Opus 4.8 (1M context) --- dashboard/src/App.svelte | 25 +- dashboard/src/components/code/CodePage.svelte | 83 + .../components/code/DiagnosticsPanel.svelte | 159 + .../src/components/code/FileSymbols.svelte | 105 + dashboard/src/components/code/FileTree.svelte | 125 + .../components/code/WorkspaceSearch.svelte | 125 + .../components/common/FilterDropdown.svelte | 214 + .../components/overview/OverviewPage.svelte | 14 + .../components/overview/SummaryCards.svelte | 82 + .../src/components/overview/Timeline.svelte | 176 + .../components/overview/TimelineRow.svelte | 149 + dashboard/src/components/shell/Header.svelte | 9 +- .../src/components/stats/ChartPanel.svelte | 71 +- .../components/stats/DrillDownPanel.svelte | 186 + .../src/components/stats/DurationChart.svelte | 10 + .../src/components/stats/RateChart.svelte | 110 + .../src/components/stats/SortSelector.svelte | 39 + .../src/components/stats/StatsPage.svelte | 50 +- dashboard/src/lib/api/endpoints.ts | 33 + dashboard/src/lib/api/types.ts | 86 + dashboard/src/lib/charts.ts | 206 +- dashboard/src/lib/format.ts | 30 + dashboard/src/lib/pollers.ts | 10 +- dashboard/src/lib/stores/code.svelte.ts | 156 + dashboard/src/lib/stores/stats.svelte.ts | 17 + dashboard/src/lib/stores/timeline.svelte.ts | 123 + dashboard/tests/charts.test.ts | 173 +- dashboard/tests/code-store.test.ts | 97 + dashboard/tests/drilldown.test.ts | 48 + dashboard/tests/endpoints.test.ts | 18 + dashboard/tests/filter-dropdown.test.ts | 71 + dashboard/tests/helpers.ts | 17 +- dashboard/tests/last-execution.test.ts | 5 + dashboard/tests/pollers.test.ts | 19 +- dashboard/tests/sort-selector.test.ts | 66 + dashboard/tests/summary-cards.test.ts | 28 + dashboard/tests/timeline-store.test.ts | 138 + dashboard/tests/timeline.test.ts | 47 + dashboard/vite.config.ts | 5 + .../2026-05-28-dashboard-v2-legacy-port.md | 7354 +++++++++++++++++ ...6-05-28-dashboard-v2-legacy-port-design.md | 417 + src/serena/agent.py | 38 +- src/serena/analytics.py | 134 +- src/serena/dashboard.py | 116 +- src/serena/dashboard_code.py | 491 ++ .../dashboard/assets/index-BBcJgPxG.css | 1 - .../dashboard/assets/index-ByT_227W.js | 30 - .../dashboard/assets/index-i8LjQRS6.css | 1 + .../dashboard/assets/index-vCsKzPK1.js | 31 + src/serena/resources/dashboard/index.html | 4 +- src/serena/task_executor.py | 39 +- src/serena/tools/tools_base.py | 39 +- test/serena/test_analytics.py | 269 + test/serena/test_dashboard.py | 200 +- test/serena/test_dashboard_code.py | 386 + test/serena/test_task_executor.py | 77 + 56 files changed, 12634 insertions(+), 118 deletions(-) create mode 100644 dashboard/src/components/code/CodePage.svelte create mode 100644 dashboard/src/components/code/DiagnosticsPanel.svelte create mode 100644 dashboard/src/components/code/FileSymbols.svelte create mode 100644 dashboard/src/components/code/FileTree.svelte create mode 100644 dashboard/src/components/code/WorkspaceSearch.svelte create mode 100644 dashboard/src/components/common/FilterDropdown.svelte create mode 100644 dashboard/src/components/overview/SummaryCards.svelte create mode 100644 dashboard/src/components/overview/Timeline.svelte create mode 100644 dashboard/src/components/overview/TimelineRow.svelte create mode 100644 dashboard/src/components/stats/DrillDownPanel.svelte create mode 100644 dashboard/src/components/stats/DurationChart.svelte create mode 100644 dashboard/src/components/stats/RateChart.svelte create mode 100644 dashboard/src/components/stats/SortSelector.svelte create mode 100644 dashboard/src/lib/stores/code.svelte.ts create mode 100644 dashboard/src/lib/stores/timeline.svelte.ts create mode 100644 dashboard/tests/code-store.test.ts create mode 100644 dashboard/tests/drilldown.test.ts create mode 100644 dashboard/tests/filter-dropdown.test.ts create mode 100644 dashboard/tests/sort-selector.test.ts create mode 100644 dashboard/tests/summary-cards.test.ts create mode 100644 dashboard/tests/timeline-store.test.ts create mode 100644 dashboard/tests/timeline.test.ts create mode 100644 docs/superpowers/plans/2026-05-28-dashboard-v2-legacy-port.md create mode 100644 docs/superpowers/specs/2026-05-28-dashboard-v2-legacy-port-design.md create mode 100644 src/serena/dashboard_code.py delete mode 100644 src/serena/resources/dashboard/assets/index-BBcJgPxG.css delete mode 100644 src/serena/resources/dashboard/assets/index-ByT_227W.js create mode 100644 src/serena/resources/dashboard/assets/index-i8LjQRS6.css create mode 100644 src/serena/resources/dashboard/assets/index-vCsKzPK1.js create mode 100644 test/serena/test_analytics.py create mode 100644 test/serena/test_dashboard_code.py diff --git a/dashboard/src/App.svelte b/dashboard/src/App.svelte index 43bc5c384..02b21bb10 100644 --- a/dashboard/src/App.svelte +++ b/dashboard/src/App.svelte @@ -8,11 +8,13 @@ import { config } from '$lib/stores/config.svelte'; import { executions } from '$lib/stores/executions.svelte'; import { logs } from '$lib/stores/logs.svelte'; + import { timeline } from '$lib/stores/timeline.svelte'; import { pageTitle } from '$lib/title'; // View components are added in Phases 3–6; import them as they land. import OverviewPage from './components/overview/OverviewPage.svelte'; import LogsPage from './components/logs/LogsPage.svelte'; import StatsPage from './components/stats/StatsPage.svelte'; + import CodePage from './components/code/CodePage.svelte'; import ModalHost from './components/modals/ModalHost.svelte'; import { modal } from '$lib/stores/modal.svelte'; @@ -30,16 +32,20 @@ const queuedPoller = createPoller(() => safe(executions.pollQueued()), 1000); const lastPoller = createPoller(() => safe(executions.pollLast()), 1000); const logsPoller = createPoller(() => safe(logs.poll()), 1000); + const timelinePoller = createPoller(() => safe(timeline.poll()), 1000); const pollers: Record = { config: configPoller, queued: queuedPoller, last: lastPoller, logs: logsPoller, + timeline: timelinePoller, }; function startPollers(v: View) { for (const p of Object.values(pollers)) p.stop(); + // Skip starting when the tab is hidden — visibility handler resumes on unhide. + if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return; for (const name of pollersForView(v)) pollers[name].start(); } @@ -51,6 +57,15 @@ onMount(() => { theme.init(); startPollers('overview'); + const onVis = () => { + if (document.visibilityState === 'visible') { + startPollers(view); + } else { + for (const p of Object.values(pollers)) p.stop(); + } + }; + document.addEventListener('visibilitychange', onVis); + return () => document.removeEventListener('visibilitychange', onVis); }); @@ -72,7 +87,15 @@ />
              {/if} {#if view === 'logs'}
              {/if} - {#if view === 'stats'}
              {/if} + {#if view === 'stats'}
              + { + timeline.setFilter(tool); + navigate('overview'); + }} + /> +
              {/if} + {#if view === 'code'}
              {/if}
              diff --git a/dashboard/src/components/code/CodePage.svelte b/dashboard/src/components/code/CodePage.svelte new file mode 100644 index 000000000..dabd4be96 --- /dev/null +++ b/dashboard/src/components/code/CodePage.svelte @@ -0,0 +1,83 @@ + + +
              + +
              + + {#if code.middle_pane === 'symbols'} + + {:else} + + {/if} +
              + +
              + + diff --git a/dashboard/src/components/code/DiagnosticsPanel.svelte b/dashboard/src/components/code/DiagnosticsPanel.svelte new file mode 100644 index 000000000..050466310 --- /dev/null +++ b/dashboard/src/components/code/DiagnosticsPanel.svelte @@ -0,0 +1,159 @@ + + +
              +
              +

              Diagnostics

              + +
              + + {#if code.diag_loading} +
              + ⚠ Computing diagnostics — this can take a while and temporarily slows other LSP-backed tools. +
              + {/if} + + {#if code.diag_error} +
              + Diagnostics failed. +
              {code.diag_error}
              +
              + {/if} + + {#if code.diag_truncated && !code.diag_loading} +
              + Showing first {code.diag_files.length} files; project has more. +
              + {/if} + + {#if code.diag_files.length === 0 && !code.diag_loading && !code.diag_error} +

              + Click Refresh to compute diagnostics. (Results are not auto-refreshed because diagnostics is + slow.) +

              + {/if} + + {#each code.diag_files as f (f.path)} +
              + + {f.path} + {f.diagnostics.length} + +
                + {#each f.diagnostics as d, i (i)} +
              • + {d.line + 1}:{d.column + 1} + {d.severity} + {d.message} + {#if d.source}[{d.source}]{/if} +
              • + {/each} +
              +
              + {/each} +
              + + diff --git a/dashboard/src/components/code/FileSymbols.svelte b/dashboard/src/components/code/FileSymbols.svelte new file mode 100644 index 000000000..09b1417ec --- /dev/null +++ b/dashboard/src/components/code/FileSymbols.svelte @@ -0,0 +1,105 @@ + + +{#snippet symbolNode(s: FileSymbol, depth: number)} +
            • + {s.kind} + {s.name} + {s.range.start.line + 1}:{s.range.start.character + 1} +
            • + {#if s.children && s.children.length > 0} + {#each s.children as c (c.name + ':' + c.range.start.line)} + {@render symbolNode(c, depth + 1)} + {/each} + {/if} +{/snippet} + +
              + {#if !path} +

              Select a file from the tree.

              + {:else if error !== undefined} +
              + Failed to load symbols. +
              {error}
              + +
              + {:else if symbols === undefined} +

              Loading…

              + {:else if symbols.length === 0} +

              No symbols.

              + {:else} +
                + {#each symbols as s (s.name + ':' + s.range.start.line)} + {@render symbolNode(s, 0)} + {/each} +
              + {/if} +
              + + diff --git a/dashboard/src/components/code/FileTree.svelte b/dashboard/src/components/code/FileTree.svelte new file mode 100644 index 000000000..31c9f698c --- /dev/null +++ b/dashboard/src/components/code/FileTree.svelte @@ -0,0 +1,125 @@ + + +{#snippet treeNode(path: string, depth: number)} + {@const children = code.dir_children[path]} + {@const error = code.dir_errors[path]} +
                + {#if error !== undefined} +
              • + + {error} +
              • + {:else if !children} +
              • + {:else} + {#each children as entry (entry.name)} + {@const fullPath = joinPath(path, entry.name)} +
              • + {#if entry.kind === 'dir'} + + {#if code.expanded.has(fullPath)} + {@render treeNode(fullPath, depth + 1)} + {/if} + {:else} + + {/if} +
              • + {/each} + {/if} +
              +{/snippet} + +{@render treeNode(rootPath, 0)} + + diff --git a/dashboard/src/components/code/WorkspaceSearch.svelte b/dashboard/src/components/code/WorkspaceSearch.svelte new file mode 100644 index 000000000..9948d9b89 --- /dev/null +++ b/dashboard/src/components/code/WorkspaceSearch.svelte @@ -0,0 +1,125 @@ + + +
              + + {#if code.search_error} +
              + Search failed. + {code.search_error} +
              + {/if} + {#if code.search_loading} +
              Searching…
              + {:else if value.trim().length >= 2 && code.search_results.length === 0 && !code.search_error} +
              No matches.
              + {/if} + {#if code.search_results.length > 0} +
                + {#each code.search_results as m (m.path + ':' + m.name + ':' + m.range.start.line)} +
              • + +
              • + {/each} +
              + {/if} +
              + + diff --git a/dashboard/src/components/common/FilterDropdown.svelte b/dashboard/src/components/common/FilterDropdown.svelte new file mode 100644 index 000000000..d90062ca7 --- /dev/null +++ b/dashboard/src/components/common/FilterDropdown.svelte @@ -0,0 +1,214 @@ + + +
              + + + {#if open} +
              + +
                + {#each filtered as o, i (o.value)} +
              • (highlight = i)} + onclick={() => selectAt(i)} + onkeydown={(e) => e.key === 'Enter' && selectAt(i)} + > + {o.value === value ? '✓' : ''} + {o.label} +
              • + {/each} + {#if filtered.length === 0} +
              • No matches
              • + {/if} +
              +
              + {/if} +
              + + diff --git a/dashboard/src/components/overview/OverviewPage.svelte b/dashboard/src/components/overview/OverviewPage.svelte index cbcf02f55..b9f37a047 100644 --- a/dashboard/src/components/overview/OverviewPage.svelte +++ b/dashboard/src/components/overview/OverviewPage.svelte @@ -1,6 +1,7 @@ {#if !d} {:else} + +
              diff --git a/dashboard/src/components/overview/SummaryCards.svelte b/dashboard/src/components/overview/SummaryCards.svelte new file mode 100644 index 000000000..4b3416830 --- /dev/null +++ b/dashboard/src/components/overview/SummaryCards.svelte @@ -0,0 +1,82 @@ + + +
              +
              +
              Calls
              +
              {totals ? formatNumber(totals.num_calls) : '—'}
              +
              + {totals ? `error rate ${errorRate.toFixed(2)} %` : ''} +
              +
              +
              +
              Tokens
              +
              {totals ? formatTokens(totals.total_tokens) : '—'}
              +
              {totals ? `total` : ''}
              +
              +
              +
              Time
              +
              {totals ? formatDurationMs(totals.total_duration_ms) : '—'}
              +
              {totals ? `avg ${formatDurationMs(avgDurationMs)} / call` : ''}
              +
              +
              +
              Errors
              +
              {totals ? formatNumber(totals.num_errors) : '—'}
              +
              {totals ? `${errorRate.toFixed(2)} % rate` : ''}
              +
              +
              + + diff --git a/dashboard/src/components/overview/Timeline.svelte b/dashboard/src/components/overview/Timeline.svelte new file mode 100644 index 000000000..18c366ef2 --- /dev/null +++ b/dashboard/src/components/overview/Timeline.svelte @@ -0,0 +1,176 @@ + + +
              +
              +

              Tool Call Timeline

              +
              + store.setFilter(v)} + /> + + +
              +
              + + {#if store.pausedGap} + + {/if} + {#if store.error} + + {/if} + +
              + + + + + page {store.page} of {totalPages} + + +
              + +
                + {#each visible as r (r.seq)} + + {/each} + {#if visible.length === 0} +
              • No calls yet.
              • + {/if} +
              +
              + + diff --git a/dashboard/src/components/overview/TimelineRow.svelte b/dashboard/src/components/overview/TimelineRow.svelte new file mode 100644 index 000000000..ad98f1fb2 --- /dev/null +++ b/dashboard/src/components/overview/TimelineRow.svelte @@ -0,0 +1,149 @@ + + +
            • + + {#if expanded} +
              +
              seq: {record.seq}
              +
              started: {new Date(record.started_at * 1000).toISOString()}
              +
              duration: {formatDurationMs(record.duration_ms)}
              + {#if record.error_message} +
              error: {record.error_message}
              + {/if} +
              +
              input:
              +
              {showFullInput || record.input_preview.length < 400
              +            ? record.input_preview
              +            : record.input_preview.slice(0, 400) + '…'}
              + {#if record.input_preview.length >= 400} + + {/if} + {#if record.input_truncated} +
              (server-truncated at 8 KB)
              + {/if} +
              +
              +
              output:
              +
              {showFullOutput || record.output_preview.length < 400
              +            ? record.output_preview
              +            : record.output_preview.slice(0, 400) + '…'}
              + {#if record.output_preview.length >= 400} + + {/if} + {#if record.output_truncated} +
              (server-truncated at 8 KB)
              + {/if} +
              +
              + {/if} +
            • + + diff --git a/dashboard/src/components/shell/Header.svelte b/dashboard/src/components/shell/Header.svelte index 5c6867ffc..10d5495df 100644 --- a/dashboard/src/components/shell/Header.svelte +++ b/dashboard/src/components/shell/Header.svelte @@ -2,7 +2,7 @@ import { theme } from '$lib/stores/theme.svelte'; import ThemeToggle from './ThemeToggle.svelte'; import BannerCarousel from '../banners/BannerCarousel.svelte'; - type View = 'overview' | 'logs' | 'stats'; + import type { View } from '$lib/pollers'; let { active, onnavigate, @@ -53,6 +53,13 @@ aria-current={active === 'stats' ? 'page' : undefined} onclick={() => onnavigate('stats')}>Stats +
              diff --git a/dashboard/src/components/stats/ChartPanel.svelte b/dashboard/src/components/stats/ChartPanel.svelte index f68aae80f..bccc3deb6 100644 --- a/dashboard/src/components/stats/ChartPanel.svelte +++ b/dashboard/src/components/stats/ChartPanel.svelte @@ -2,11 +2,21 @@ import { untrack } from 'svelte'; import Chart from 'chart.js/auto'; import ChartDataLabels from 'chartjs-plugin-datalabels'; - import type { ChartConfiguration, ChartDataset } from 'chart.js'; + import type { ChartConfiguration, ChartDataset, ChartEvent, ActiveElement } from 'chart.js'; import type { ChartSpec } from '$lib/charts'; import { theme } from '$lib/stores/theme.svelte'; - let { title, spec, height = 240 }: { title: string; spec: ChartSpec; height?: number } = $props(); + let { + title, + spec, + height = 240, + onSliceClick, + }: { + title?: string; + spec: ChartSpec; + height?: number; + onSliceClick?: (_label: string) => void; + } = $props(); let canvas = $state(null); let chart: Chart | null = null; @@ -37,15 +47,28 @@ if (spec.type === 'pie') { chart.data.datasets[0].backgroundColor = sliceColors; - } else { - // Dual-axis bar: input semi-transparent fill + solid border, output solid. - // Narrow once — `ChartDataset<'pie' | 'bar'>` resolves `borderWidth` to `never`. + } else if (spec.type === 'bar' && chart.data.datasets.length === 2) { + // Dual-axis bar (tokens / duration): input semi-transparent fill + solid + // border, output solid. Narrow once — `ChartDataset<'pie'|'bar'>` resolves + // `borderWidth` to `never`. const input = chart.data.datasets[0] as ChartDataset<'bar'>; const output = chart.data.datasets[1] as ChartDataset<'bar'>; input.backgroundColor = sliceColors.map((c) => c + '80'); input.borderColor = sliceColors; input.borderWidth = 2; output.backgroundColor = sliceColors; + } else if (spec.type === 'line') { + // Per-dataset colours indexed by dataset position (tools or single line). + chart.data.datasets.forEach((ds, i) => { + const lineDs = ds as ChartDataset<'line'>; + const col = colors[i % colors.length]; + lineDs.borderColor = col; + // Stacked-area uses semi-transparent fill; the single-line non-stacked + // dataset has fill:false so backgroundColor doesn't matter visually. + lineDs.backgroundColor = col + '66'; + lineDs.pointBackgroundColor = col; + lineDs.pointBorderColor = col; + }); } const o = chart.options as ChartConfiguration['options'] & { @@ -69,13 +92,27 @@ // Create once per canvas bind. Reads `spec` via untrack so data changes don't // recreate the chart (the data-effect below handles those in place). Registers - // datalabels per-instance instead of globally. + // datalabels per-instance instead of globally. Splice onSliceClick into + // options.onClick BEFORE construction so it survives data updates (B23). $effect(() => { if (!canvas) return; const node = canvas; - const created = untrack( - () => new Chart(node, { ...spec, plugins: [ChartDataLabels] } as ChartConfiguration), - ); + const created = untrack(() => { + const config = { ...spec, plugins: [ChartDataLabels] } as ChartConfiguration; + const baseOnClick = (config.options as { onClick?: unknown } | undefined)?.onClick as + | ((_evt: ChartEvent, _elements: ActiveElement[], _chartInstance: Chart) => void) + | undefined; + const handler = (evt: ChartEvent, elements: ActiveElement[], chartInstance: Chart) => { + if (elements.length > 0 && onSliceClick) { + const idx = elements[0].index; + const label = chartInstance.data.labels?.[idx]; + if (typeof label === 'string') onSliceClick(label); + } + if (typeof baseOnClick === 'function') baseOnClick(evt, elements, chartInstance); + }; + config.options = { ...(config.options ?? {}), onClick: handler }; + return new Chart(node, config); + }); chart = created; untrack(() => applyTheme()); return () => { @@ -85,13 +122,21 @@ }); // Data-only updates: copy labels/data from the new spec and recolour in place. + // B25: if the dataset COUNT changed (e.g. RateChart stacked toggle flips 1↔N), + // replace the array wholesale — mutating dataset[i].data only handles same-shape + // updates and silently drops added datasets. $effect(() => { const next = spec; if (!chart) return; chart.data.labels = next.data.labels ?? []; - next.data.datasets.forEach((ds, i) => { - if (chart!.data.datasets[i]) chart!.data.datasets[i].data = ds.data; - }); + if (chart.data.datasets.length !== next.data.datasets.length) { + chart.data.datasets = next.data.datasets; + chart.update('none'); + } else { + next.data.datasets.forEach((ds, i) => { + if (chart!.data.datasets[i]) chart!.data.datasets[i].data = ds.data; + }); + } applyTheme(); }); @@ -103,7 +148,7 @@
              -

              {title}

              + {#if title}

              {title}

              {/if}
              diff --git a/dashboard/src/components/stats/DrillDownPanel.svelte b/dashboard/src/components/stats/DrillDownPanel.svelte new file mode 100644 index 000000000..308f7a9d7 --- /dev/null +++ b/dashboard/src/components/stats/DrillDownPanel.svelte @@ -0,0 +1,186 @@ + + +{#if tool} + +{/if} + + diff --git a/dashboard/src/components/stats/DurationChart.svelte b/dashboard/src/components/stats/DurationChart.svelte new file mode 100644 index 000000000..3305bd75d --- /dev/null +++ b/dashboard/src/components/stats/DurationChart.svelte @@ -0,0 +1,10 @@ + + + diff --git a/dashboard/src/components/stats/RateChart.svelte b/dashboard/src/components/stats/RateChart.svelte new file mode 100644 index 000000000..941a2385e --- /dev/null +++ b/dashboard/src/components/stats/RateChart.svelte @@ -0,0 +1,110 @@ + + +
              +
              +

              Tool Call Rate

              +
              + + + {#if stacked} + + + {/if} +
              +
              + + {#if windowMinutes >= 60} +

              + Buffer is capped at 2000 records — long windows may show only the recent portion. +

              + {/if} +
              + + diff --git a/dashboard/src/components/stats/SortSelector.svelte b/dashboard/src/components/stats/SortSelector.svelte new file mode 100644 index 000000000..fba675072 --- /dev/null +++ b/dashboard/src/components/stats/SortSelector.svelte @@ -0,0 +1,39 @@ + + + + + diff --git a/dashboard/src/components/stats/StatsPage.svelte b/dashboard/src/components/stats/StatsPage.svelte index a2ea06afc..46cf4a24f 100644 --- a/dashboard/src/components/stats/StatsPage.svelte +++ b/dashboard/src/components/stats/StatsPage.svelte @@ -4,8 +4,17 @@ import { pieSpec, tokensBarSpec } from '$lib/charts'; import ChartPanel from './ChartPanel.svelte'; import StatsSummary from './StatsSummary.svelte'; + import SortSelector from './SortSelector.svelte'; + import DurationChart from './DurationChart.svelte'; + import RateChart from './RateChart.svelte'; + import DrillDownPanel from './DrillDownPanel.svelte'; import Button from '../common/Button.svelte'; + + let { onOpenInTimeline }: { onOpenInTimeline?: (_tool: string) => void } = $props(); + const hasStats = $derived(Object.keys(stats.stats).length > 0); + const drill = (label: string) => stats.setDrillTool(label); + onMount(() => { void stats.refresh(); }); @@ -14,23 +23,53 @@
              + +
              {#if hasStats}
              Token estimator: {stats.estimator}
              - - - + + + +
              +
              +
              - + +
              +
              +
              {:else}
              No tool stats collected yet.
              {/if} + + +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `cd dashboard && npm test -- filter-dropdown && npm run check` + +Expected: PASS. + +- [ ] **Step 5: Commit (no asset rebuild — final phase rebuilds once for the whole branch)** + +```bash +git add dashboard/src/components/common/FilterDropdown.svelte dashboard/tests/filter-dropdown.test.ts +git commit -m "$(cat <<'EOF' +feat(dashboard): shared FilterDropdown primitive + +Filtered single-select with × clear, applied checkmark, substring +typing, keyboard nav (Up/Down/Enter/Esc), and click-outside close. +Active border uses the orange accent token. Reused by the Timeline +filter and any future filtered list. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 12: Create `SummaryCards.svelte` and wire it into Overview + +**Files:** +- Create: `dashboard/src/components/overview/SummaryCards.svelte` +- Modify: `dashboard/src/components/overview/StatsSummary.svelte` (replace inline KPI strip) +- Create: `dashboard/tests/summary-cards.test.ts` + +- [ ] **Step 1: Write the failing test** + +Create `dashboard/tests/summary-cards.test.ts`: + +```ts +import { describe, it, expect } from 'vitest'; +import { render } from '@testing-library/svelte'; +import SummaryCards from '../src/components/overview/SummaryCards.svelte'; +import type { ToolStatsTotals } from '../src/lib/api/types'; + +const totals: ToolStatsTotals = { + num_calls: 1234, + num_errors: 7, + total_duration_ms: 90123, + total_tokens: 5678, +}; + +describe('SummaryCards', () => { + it('renders four cards with formatted values', () => { + const { getAllByRole, container } = render(SummaryCards, { totals }); + const headings = container.querySelectorAll('[data-card-title]'); + expect(headings.length).toBe(4); + const labels = Array.from(headings).map((n) => n.textContent?.trim()); + expect(labels).toEqual(['Calls', 'Tokens', 'Time', 'Errors']); + expect(container.textContent).toContain('1,234'); + expect(container.textContent).toContain('7'); + }); + + it('renders em-dashes when totals is undefined (older backend)', () => { + const { container } = render(SummaryCards, { totals: undefined }); + expect(container.textContent).toContain('—'); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cd dashboard && npm test -- summary-cards` + +Expected: FAIL — component missing. + +- [ ] **Step 3: Create `dashboard/src/components/overview/SummaryCards.svelte`** + +```svelte + + +
              +
              +
              Calls
              +
              {totals ? formatNumber(totals.num_calls) : '—'}
              +
              + {totals ? `error rate ${errorRate.toFixed(2)} %` : ''} +
              +
              +
              +
              Tokens
              +
              {totals ? formatTokens(totals.total_tokens) : '—'}
              +
              {totals ? `total` : ''}
              +
              +
              +
              Time
              +
              {totals ? formatDurationMs(totals.total_duration_ms) : '—'}
              +
              {totals ? `avg ${formatDurationMs(avgDurationMs)} / call` : ''}
              +
              +
              +
              Errors
              +
              {totals ? formatNumber(totals.num_errors) : '—'}
              +
              {totals ? `${errorRate.toFixed(2)} % rate` : ''}
              +
              +
              + + +``` + +- [ ] **Step 4: Add formatters in `dashboard/src/lib/format.ts` if missing** + +Read `dashboard/src/lib/format.ts` first. If `formatNumber`, `formatDurationMs`, `formatTokens` already exist, skip this step. Otherwise append: + +```ts +export function formatNumber(n: number): string { + return n.toLocaleString('en-US'); +} + +export function formatDurationMs(ms: number): string { + if (ms < 1) return '<1 ms'; + if (ms < 1000) return `${Math.round(ms)} ms`; + const s = ms / 1000; + if (s < 60) return `${s.toFixed(1)} s`; + const m = s / 60; + if (m < 60) return `${m.toFixed(1)} m`; + const h = m / 60; + return `${h.toFixed(1)} h`; +} + +export function formatTokens(n: number): string { + if (n < 1000) return String(n); + if (n < 1_000_000) return `${(n / 1000).toFixed(1)}k`; + return `${(n / 1_000_000).toFixed(2)}M`; +} +``` + +- [ ] **Step 5: Wire `SummaryCards` into `StatsSummary.svelte`** + +Open `dashboard/src/components/overview/StatsSummary.svelte`. Find the existing inline KPI strip (the section that renders Calls / Input tokens / Output tokens / Total tokens). Replace it with: + +```svelte + + + +``` + +> **Implementer note:** the rest of `StatsSummary.svelte` may render other content (e.g. estimator name, totals not covered by SummaryCards). Preserve that content; only the four-stat strip is replaced. Re-read the file before editing. + +- [ ] **Step 6: Run tests to verify they pass** + +Run: `cd dashboard && npm test -- summary-cards && npm run check` + +Expected: PASS. + +- [ ] **Step 7: Commit** + +```bash +git add dashboard/src/components/overview/SummaryCards.svelte dashboard/src/components/overview/StatsSummary.svelte dashboard/src/lib/format.ts dashboard/tests/summary-cards.test.ts +git commit -m "$(cat <<'EOF' +feat(dashboard): 4-card Overview KPI strip (Calls/Tokens/Time/Errors) + +Replaces the inline Calls/Input/Output/Total strip with four cards: +Calls, Tokens, Time (total + avg/call), Errors. Reads +tool_stats_totals from the existing config-overview payload, so it +updates on the 1s poll without a new endpoint. Falls back to em-dashes +when the backend doesn't expose the field (forward-compat). + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 13: Create `Timeline.svelte` + `TimelineRow.svelte` + +**Files:** +- Create: `dashboard/src/components/overview/Timeline.svelte` +- Create: `dashboard/src/components/overview/TimelineRow.svelte` +- Create: `dashboard/tests/timeline.test.ts` + +- [ ] **Step 1: Write the failing test** + +Create `dashboard/tests/timeline.test.ts`: + +```ts +import { describe, it, expect } from 'vitest'; +import { render, fireEvent } from '@testing-library/svelte'; +import Timeline from '../src/components/overview/Timeline.svelte'; +import { createTimelineStore } from '../src/lib/stores/timeline.svelte'; +import type { ToolCallRecord } from '../src/lib/api/types'; + +function rec(seq: number, tool = 'read_file', success = true): ToolCallRecord { + return { + seq, + tool, + started_at: 1000 + seq, + duration_ms: seq, + success, + error_message: success ? null : 'Err', + input_preview: `in${seq}`, + output_preview: `out${seq}`, + input_truncated: false, + output_truncated: false, + }; +} + +describe('Timeline', () => { + it('renders rows from the store and paginates', () => { + const store = createTimelineStore(); + for (let i = 1; i <= 60; i++) (store as any).records.push(rec(i)); + // Force reactive read by reassigning page through public API + store.setPageSize(25); + const { container } = render(Timeline, { store, toolNames: ['read_file'] }); + // 25 rows on page 1 + expect(container.querySelectorAll('[data-timeline-row]').length).toBe(25); + }); + + it('toggle pause sets paused state', async () => { + const store = createTimelineStore(); + const { getByLabelText } = render(Timeline, { store, toolNames: [] }); + await fireEvent.click(getByLabelText(/pause/i)); + expect(store.paused).toBe(true); + }); + + it('clear view empties records', async () => { + const store = createTimelineStore(); + (store as any).records.push(rec(1)); + const { getByLabelText } = render(Timeline, { store, toolNames: [] }); + await fireEvent.click(getByLabelText(/clear/i)); + expect(store.records.length).toBe(0); + }); +}); +``` + +> **Note:** the test mutates `store.records` via cast for setup. Production code never does this — only the store's own actions touch state. + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cd dashboard && npm test -- timeline.test` + +Expected: FAIL — component missing. + +- [ ] **Step 3: Create `dashboard/src/components/overview/TimelineRow.svelte`** + +```svelte + + +
            • + + {#if expanded} +
              +
              seq: {record.seq}
              +
              started: {new Date(record.started_at * 1000).toISOString()}
              +
              duration: {formatDurationMs(record.duration_ms)}
              + {#if record.error_message} +
              error: {record.error_message}
              + {/if} +
              +
              input:
              +
              {showFullInput || record.input_preview.length < 400 ? record.input_preview : record.input_preview.slice(0, 400) + '…'}
              + {#if record.input_preview.length >= 400} + + {/if} + {#if record.input_truncated} +
              (server-truncated at 8 KB)
              + {/if} +
              +
              +
              output:
              +
              {showFullOutput || record.output_preview.length < 400 ? record.output_preview : record.output_preview.slice(0, 400) + '…'}
              + {#if record.output_preview.length >= 400} + + {/if} + {#if record.output_truncated} +
              (server-truncated at 8 KB)
              + {/if} +
              +
              + {/if} +
            • + + +``` + +- [ ] **Step 4: Create `dashboard/src/components/overview/Timeline.svelte`** + +```svelte + + +
              +
              +

              Tool Call Timeline

              +
              + store.setFilter(v)} + /> + + +
              +
              + + {#if store.pausedGap} + + {/if} + {#if store.error} + + {/if} + +
              + + + + + page {store.page} of {totalPages} + + +
              + +
                + {#each visible as r (r.seq)} + + {/each} + {#if visible.length === 0} +
              • No calls yet.
              • + {/if} +
              +
              + + +``` + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `cd dashboard && npm test -- timeline.test && npm run check` + +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add dashboard/src/components/overview/Timeline.svelte dashboard/src/components/overview/TimelineRow.svelte dashboard/tests/timeline.test.ts +git commit -m "$(cat <<'EOF' +feat(dashboard): Tool Call Timeline + expandable TimelineRow + +Live, paginated, filter-able list of tool calls. Per-tool +FilterDropdown, pause/resume, clear-view, page-size selector, +first/prev/next/last paging. Rows expand inline to show seq, full +timestamp, duration, input/output previews with show-full toggle and +8 KB truncation note. Paused-gap banner appears when poll skipped +more records than the response window can carry. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 14: Register the timeline poller + tool-names fetch + mount Timeline on Overview + +**Files:** +- Modify: `dashboard/src/lib/pollers.ts` +- Modify: `dashboard/src/App.svelte` (mount Timeline, start `timeline` poller) +- Modify: `dashboard/src/components/overview/StatsSummary.svelte` (place Timeline below SummaryCards) — or whichever Overview-level component composes the cards +- Create: `dashboard/tests/pollers.test.ts` (extend if exists) + +- [ ] **Step 1: Update `dashboard/src/lib/pollers.ts`** + +```ts +export type View = 'overview' | 'logs' | 'stats' | 'code'; +export type PollerName = 'config' | 'queued' | 'last' | 'logs' | 'timeline'; + +/** Which pollers should be running for a given view. Pure so it is unit-testable. */ +export function pollersForView(view: View): PollerName[] { + switch (view) { + case 'overview': + return ['config', 'queued', 'last', 'timeline']; + case 'logs': + return ['logs']; + case 'stats': + return ['config', 'timeline']; + case 'code': + return ['config']; + } +} +``` + +- [ ] **Step 2: Add / update unit test for pollers** + +If `dashboard/tests/pollers.test.ts` doesn't exist, create it: + +```ts +import { describe, it, expect } from 'vitest'; +import { pollersForView } from '../src/lib/pollers'; + +describe('pollersForView', () => { + it('includes timeline for overview and stats', () => { + expect(pollersForView('overview')).toContain('timeline'); + expect(pollersForView('stats')).toContain('timeline'); + }); + it('does not run timeline on logs/code', () => { + expect(pollersForView('logs')).not.toContain('timeline'); + expect(pollersForView('code')).not.toContain('timeline'); + }); +}); +``` + +- [ ] **Step 3: Wire the timeline poller in `dashboard/src/App.svelte`** + +Read the existing `App.svelte` first to see how `config`, `queued`, `last`, `logs` pollers are created and started/stopped on view changes. The pattern is `createPoller(fn, intervalMs)`. Add a `timeline` poller alongside the others: + +```ts +import { timeline } from '$lib/stores/timeline.svelte'; +import { fetchToolNames } from '$lib/api/endpoints'; +// …existing imports + +const timelinePoller = createPoller(() => safe(() => timeline.poll()), 1000); + +// Visibility-aware: pause polls when tab is hidden. +$effect(() => { + function onVis() { + if (document.visibilityState === 'visible') { + // resume by re-applying current view's pollers + applyPollersForView(view); + } else { + timelinePoller.stop(); + // …stop other pollers as the existing code does + } + } + document.addEventListener('visibilitychange', onVis); + return () => document.removeEventListener('visibilitychange', onVis); +}); +``` + +> **Implementer note:** the existing `App.svelte` already orchestrates pollers via `pollersForView` + a per-name map. Add `'timeline': timelinePoller` to that map, and add a tool-names fetch (cached) so the Timeline can populate FilterDropdown. Match the existing pattern, don't introduce a new one. + +- [ ] **Step 4: Mount `` below `` on the Overview page** + +Find the Overview-level component (likely `dashboard/src/components/overview/OverviewPage.svelte` or `StatsSummary.svelte` depending on which currently composes Overview). Insert Timeline below SummaryCards: + +```svelte + + + + +``` + +- [ ] **Step 5: Run tests + type-check** + +Run: `cd dashboard && npm test -- pollers && npm run check` + +Expected: PASS. + +- [ ] **Step 6: Manual smoke (dev server)** + +Run the emulator (see memory `run-dashboard-emulate-tool-calls.md`) in one terminal, then `cd dashboard && npm run dev` in another. Visit `http://localhost:5273`, confirm: + +- 4 SummaryCards appear under Overview. +- Timeline section appears below, ticking in new rows once per second. +- Pause/Resume button works. +- Tool filter dropdown opens, lets you type, applies a filter. + +- [ ] **Step 7: Commit** + +```bash +git add dashboard/src/lib/pollers.ts dashboard/src/App.svelte dashboard/src/components/overview/*.svelte dashboard/tests/pollers.test.ts +git commit -m "$(cat <<'EOF' +feat(dashboard): mount Timeline on Overview + register timeline poller + +Timeline section sits below SummaryCards; timeline poller runs on +both Overview and Stats views (so RateChart/DurationChart/DrillDown +stay fresh on Stats) and pauses on tab-hidden. New 'code' view +registered in the polling map even though it polls nothing — its +on-demand fetches live on the components. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +## Phase 4 — Frontend Tier 3 + Stats polish + +> **⚠️ Corrections that apply to Phase 4 (see top of plan for details):** +> - **B21:** Task 17's stacked-area config is incomplete — needs `scales.{x,y}.stacked: true` on chart options, not just `fill: true` on datasets. +> - **B22:** Task 16 + 17's `ChartSpec` shape is wrong (returns `{type, labels, datasets}` at top level; should nest under `data:` per `ChartConfiguration<...>`). Tests assert the wrong shape too. Use the corrected `durationChartSpec` / `rateChartSpec` near the top of this plan. +> - **B23:** Task 18 must add chart click-handler wiring on `ChartPanel.svelte` (Step 0). +> - **B24:** Task 18's `percentile` algorithm is off-by-one. Use inclusive-linear `floor((q/100)*(n-1))` per the corrected version near the top. +> - **B25:** Task 17 must update `ChartPanel.svelte` to handle dataset-count changes (RateChart toggles between 1 and N datasets). Corrected effect snippet near the top. +> - **B26:** Task 15 — pies keep their own metric for ordering; only the tokens bar and DurationChart fully reorder by `sortKey`. Document this in Step 3. +> - **B27:** depends on Task 1 widening `ResponseToolStats.stats` type (see B2). + +### Task 15: `SortSelector.svelte` driving stats chart order + +**Files:** +- Create: `dashboard/src/components/stats/SortSelector.svelte` +- Modify: `dashboard/src/lib/stores/stats.svelte.ts` (add `sortKey` state) +- Modify: `dashboard/src/lib/charts.ts` (accept sort key in spec builders) +- Modify: `dashboard/src/components/stats/StatsPage.svelte` (mount SortSelector, pass sort key to specs) +- Create: `dashboard/tests/sort-selector.test.ts` + +- [ ] **Step 1: Write the failing test** + +Create `dashboard/tests/sort-selector.test.ts`: + +```ts +import { describe, it, expect } from 'vitest'; +import { sortToolsBy } from '../src/lib/charts'; +import type { ToolStats } from '../src/lib/api/types'; + +const stats: ToolStats = { + read_file: { num_times_called: 10, input_tokens: 100, output_tokens: 100 }, + find_symbol: { num_times_called: 50, input_tokens: 20, output_tokens: 20 }, + shell: { num_times_called: 2, input_tokens: 500, output_tokens: 500 }, +}; + +describe('sortToolsBy', () => { + it('sorts by calls desc', () => { + expect(sortToolsBy(stats, 'calls')).toEqual(['find_symbol', 'read_file', 'shell']); + }); + it('sorts by tokens desc', () => { + expect(sortToolsBy(stats, 'tokens')).toEqual(['shell', 'read_file', 'find_symbol']); + }); +}); +``` + +> If the existing `charts.ts` already exports tool ordering, replace with whatever name is in use — keep the test asserting the same behavior. + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd dashboard && npm test -- sort-selector` + +Expected: FAIL — `sortToolsBy` undefined. + +- [ ] **Step 3: Add `sortKey` state to `dashboard/src/lib/stores/stats.svelte.ts`** + +Replace the file with: + +```ts +import { fetchToolStats, clearToolStats, fetchEstimatorName } from '$lib/api/endpoints'; +import type { ToolStats } from '$lib/api/types'; + +export type SortKey = 'calls' | 'tokens' | 'duration_total' | 'duration_avg' | 'errors'; + +export function createStatsStore() { + let stats = $state({}); + let estimator = $state('unknown'); + let sortKey = $state('calls'); + + return { + get stats() { + return stats; + }, + get estimator() { + return estimator; + }, + get sortKey() { + return sortKey; + }, + setSortKey(k: SortKey) { + sortKey = k; + }, + async refresh() { + stats = (await fetchToolStats()).stats; + estimator = (await fetchEstimatorName()).token_count_estimator_name; + }, + async clear() { + await clearToolStats(); + stats = {}; + }, + }; +} + +export const stats = createStatsStore(); +``` + +> **Note:** `ToolStatEntry` in `types.ts` should also gain optional duration/error fields to mirror the backend extension done in Phase 1. Update `types.ts`: +> +> ```ts +> export interface ToolStatEntry { +> num_times_called: number; +> num_errors?: number; +> input_tokens: number; +> output_tokens: number; +> total_duration_ms?: number; +> min_duration_ms?: number | null; +> max_duration_ms?: number | null; +> last_called_at?: number | null; +> } +> ``` + +- [ ] **Step 4: Add `sortToolsBy` to `dashboard/src/lib/charts.ts`** + +Read `dashboard/src/lib/charts.ts` first to understand existing spec-builder shape. Append: + +```ts +import type { ToolStats } from './api/types'; +import type { SortKey } from './stores/stats.svelte'; + +export function sortToolsBy(stats: ToolStats, key: SortKey): string[] { + const entries = Object.entries(stats); + const score = (e: [string, ToolStats[string]]) => { + const [, v] = e; + switch (key) { + case 'calls': + return v.num_times_called; + case 'tokens': + return (v.input_tokens ?? 0) + (v.output_tokens ?? 0); + case 'duration_total': + return v.total_duration_ms ?? 0; + case 'duration_avg': + return v.num_times_called > 0 ? (v.total_duration_ms ?? 0) / v.num_times_called : 0; + case 'errors': + return v.num_errors ?? 0; + } + }; + return entries.sort((a, b) => score(b) - score(a)).map(([name]) => name); +} +``` + +Update the existing chart spec builders (e.g. `pieSpec`, `tokensBarSpec`) to accept and apply the sort key. Read each one first; the typical change is adding a `sortKey` param and replacing any implicit ordering with `sortToolsBy(stats, sortKey)`. + +- [ ] **Step 5: Create `dashboard/src/components/stats/SortSelector.svelte`** + +```svelte + + + + + +``` + +- [ ] **Step 6: Mount `` in `StatsPage.svelte` toolbar; pass `stats.sortKey` to each existing chart spec call** + +Read `dashboard/src/components/stats/StatsPage.svelte` first. Add `` to the existing card-style toolbar (the grouping introduced by commit `a7e91c3b`). Where chart spec builders are invoked (e.g. `pieSpec(stats.stats, ...)`), pass `stats.sortKey` as the new parameter. + +- [ ] **Step 7: Run tests + type-check** + +Run: `cd dashboard && npm test -- sort-selector && npm run check` + +Expected: PASS. + +- [ ] **Step 8: Commit** + +```bash +git add dashboard/src/components/stats/SortSelector.svelte dashboard/src/lib/charts.ts dashboard/src/lib/stores/stats.svelte.ts dashboard/src/lib/api/types.ts dashboard/src/components/stats/StatsPage.svelte dashboard/tests/sort-selector.test.ts +git commit -m "$(cat <<'EOF' +feat(dashboard): SortSelector for Stats charts (calls/tokens/duration/errors) + +Single sortKey on the stats store flows into every chart spec builder +via sortToolsBy(). Default 'calls' matches today's implicit order. +ToolStatEntry mirrors the backend Entry extension (optional duration ++ error fields). + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 16: `DurationChart.svelte` (avg + max bars) + +**Files:** +- Modify: `dashboard/src/lib/charts.ts` (add `durationChartSpec`) +- Create: `dashboard/src/components/stats/DurationChart.svelte` +- Modify: `dashboard/tests/charts.test.ts` +- Modify: `dashboard/src/components/stats/StatsPage.svelte` (mount DurationChart) + +- [ ] **Step 1: Write the failing test** + +Append to `dashboard/tests/charts.test.ts`: + +```ts +import { durationChartSpec } from '../src/lib/charts'; +import type { ToolStats } from '../src/lib/api/types'; + +describe('durationChartSpec', () => { + it('builds avg + max datasets ordered by sortKey', () => { + const stats: ToolStats = { + a: { num_times_called: 10, input_tokens: 0, output_tokens: 0, total_duration_ms: 100, max_duration_ms: 20 }, + b: { num_times_called: 5, input_tokens: 0, output_tokens: 0, total_duration_ms: 50, max_duration_ms: 30 }, + }; + const spec = durationChartSpec(stats, 'duration_total'); + expect(spec.type).toBe('bar'); + expect(spec.labels).toEqual(['a', 'b']); + expect(spec.datasets.length).toBe(2); + const avg = spec.datasets.find((d) => d.label === 'Avg'); + expect(avg?.data).toEqual([10, 10]); // 100/10, 50/5 + const max = spec.datasets.find((d) => d.label === 'Max'); + expect(max?.data).toEqual([20, 30]); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd dashboard && npm test -- charts.test` + +Expected: FAIL — `durationChartSpec` undefined. + +- [ ] **Step 3: Add `durationChartSpec` in `dashboard/src/lib/charts.ts`** + +```ts +import type { ToolStats } from './api/types'; +import { sortToolsBy } from './charts'; +import type { SortKey } from './stores/stats.svelte'; + +export function durationChartSpec(stats: ToolStats, sortKey: SortKey) { + const labels = sortToolsBy(stats, sortKey); + const avg = labels.map((n) => { + const e = stats[n]; + return e && e.num_times_called > 0 ? (e.total_duration_ms ?? 0) / e.num_times_called : 0; + }); + const max = labels.map((n) => stats[n]?.max_duration_ms ?? 0); + return { + type: 'bar' as const, + labels, + datasets: [ + { label: 'Avg', data: avg, kind: 'avg' as const }, + { label: 'Max', data: max, kind: 'max' as const }, + ], + }; +} +``` + +> **Implementer note:** match the existing `ChartSpec` shape. If the codebase uses a named type rather than the literal shape above, conform to it. Read `charts.ts` first. + +- [ ] **Step 4: Create `dashboard/src/components/stats/DurationChart.svelte`** + +```svelte + + + +``` + +> **Implementer note:** `ChartPanel`'s API — read it first. The exact prop names may differ (e.g. `chartSpec` not `spec`). Conform to the existing prop signature. The chart should render two bar datasets per tool; if `ChartPanel` doesn't yet support a "max-as-outline" variant, render plain side-by-side bars — the visual variant can land later as polish, but two datasets per tool is required. + +- [ ] **Step 5: Mount in `StatsPage.svelte`** + +Add a new `` panel alongside the existing pies and tokens bar. + +- [ ] **Step 6: Run tests + check** + +Run: `cd dashboard && npm test -- charts && npm run check` + +Expected: PASS. + +- [ ] **Step 7: Commit** + +```bash +git add dashboard/src/lib/charts.ts dashboard/src/components/stats/DurationChart.svelte dashboard/src/components/stats/StatsPage.svelte dashboard/tests/charts.test.ts +git commit -m "$(cat <<'EOF' +feat(dashboard): DurationChart (avg + max ms per tool) + +Avg derived from total_duration_ms / num_times_called; max from the +new max_duration_ms field. Ordered by SortSelector key. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 17: `RateChart.svelte` (per-minute line + window + stacked-area toggle) + +**Files:** +- Modify: `dashboard/src/lib/charts.ts` (add `rateChartSpec` + bucketing helper) +- Create: `dashboard/src/components/stats/RateChart.svelte` +- Modify: `dashboard/tests/charts.test.ts` +- Modify: `dashboard/src/components/stats/StatsPage.svelte` (mount RateChart) + +- [ ] **Step 1: Write the failing test (bucket alignment regression)** + +Append to `dashboard/tests/charts.test.ts`: + +```ts +import { rateChartSpec, bucketRecordsByMinute } from '../src/lib/charts'; +import type { ToolCallRecord } from '../src/lib/api/types'; + +function rec(started_at: number, tool = 't'): ToolCallRecord { + return { + seq: 0, + tool, + started_at, + duration_ms: 0, + success: true, + error_message: null, + input_preview: '', + output_preview: '', + input_truncated: false, + output_truncated: false, + }; +} + +describe('bucketRecordsByMinute', () => { + it('emits windowMinutes + 1 buckets, with the current minute as the last bucket', () => { + const now = 1_700_000_000; // arbitrary epoch s + // Place "now" at the start of a clean minute boundary + const nowAligned = Math.floor(now / 60) * 60; + const records = [ + rec(nowAligned + 5), // in current minute + rec(nowAligned - 30), // in current minute (still within 0–59s of nowAligned) + rec(nowAligned - 65), // 1 minute ago + ]; + const buckets = bucketRecordsByMinute(records, nowAligned + 10, 15); + expect(buckets.length).toBe(16); // 15 + 1 + // Current-minute calls land in the LAST bucket — not dropped. + expect(buckets[buckets.length - 1].count).toBe(2); + expect(buckets[buckets.length - 2].count).toBe(1); + }); + + it('returns 0-count buckets for empty minutes', () => { + const now = 1_700_000_000; + const buckets = bucketRecordsByMinute([], now, 5); + expect(buckets.length).toBe(6); + expect(buckets.every((b) => b.count === 0)).toBe(true); + }); +}); + +describe('rateChartSpec', () => { + it('produces a single line dataset when stacked=false', () => { + const spec = rateChartSpec([], Date.now() / 1000, 15, false, []); + expect(spec.type).toBe('line'); + expect(spec.datasets.length).toBe(1); + }); + it('produces per-tool datasets when stacked=true and tools provided', () => { + const spec = rateChartSpec([], Date.now() / 1000, 15, true, ['a', 'b', 'c']); + expect(spec.datasets.length).toBe(3); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd dashboard && npm test -- charts` + +Expected: FAIL — functions undefined. + +- [ ] **Step 3: Add bucketing + spec in `dashboard/src/lib/charts.ts`** + +```ts +import type { ToolCallRecord } from './api/types'; + +export interface MinuteBucket { + // epoch seconds for the START of this minute bucket + start: number; + count: number; + byTool: Record; +} + +/** + * Bucket records into per-minute counts. Returns windowMinutes+1 buckets aligned + * so the current minute (containing nowEpochS) is the LAST bucket. + */ +export function bucketRecordsByMinute( + records: ToolCallRecord[], + nowEpochS: number, + windowMinutes: number, +): MinuteBucket[] { + const currentMinuteStart = Math.floor(nowEpochS / 60) * 60; + const oldestMinuteStart = currentMinuteStart - windowMinutes * 60; + const total = windowMinutes + 1; + const buckets: MinuteBucket[] = Array.from({ length: total }, (_, i) => ({ + start: oldestMinuteStart + i * 60, + count: 0, + byTool: {}, + })); + for (const r of records) { + if (r.started_at < oldestMinuteStart) continue; + if (r.started_at >= currentMinuteStart + 60) continue; + const idx = Math.floor((r.started_at - oldestMinuteStart) / 60); + if (idx < 0 || idx >= total) continue; + buckets[idx].count += 1; + buckets[idx].byTool[r.tool] = (buckets[idx].byTool[r.tool] ?? 0) + 1; + } + return buckets; +} + +export function rateChartSpec( + records: ToolCallRecord[], + nowEpochS: number, + windowMinutes: number, + stacked: boolean, + tools: string[], +) { + const buckets = bucketRecordsByMinute(records, nowEpochS, windowMinutes); + const labels = buckets.map((b) => new Date(b.start * 1000).toISOString().slice(11, 16)); + if (!stacked) { + return { + type: 'line' as const, + labels, + datasets: [{ label: 'Calls / min', data: buckets.map((b) => b.count) }], + }; + } + return { + type: 'line' as const, + labels, + stacked: true, + datasets: tools.map((t) => ({ + label: t, + data: buckets.map((b) => b.byTool[t] ?? 0), + fill: true, + })), + }; +} +``` + +- [ ] **Step 4: Create `dashboard/src/components/stats/RateChart.svelte`** + +```svelte + + +
              +
              +

              Tool Call Rate

              +
              + + + {#if stacked} + + + {/if} +
              +
              + + {#if windowMinutes >= 60} +

              Buffer is capped at 2000 records — long windows may show only the recent portion.

              + {/if} +
              + + +``` + +- [ ] **Step 5: Mount in `StatsPage.svelte`** + +Add `` to the StatsPage layout. + +- [ ] **Step 6: Run tests + check** + +Run: `cd dashboard && npm test -- charts && npm run check` + +Expected: PASS. + +- [ ] **Step 7: Commit** + +```bash +git add dashboard/src/lib/charts.ts dashboard/src/components/stats/RateChart.svelte dashboard/src/components/stats/StatsPage.svelte dashboard/tests/charts.test.ts +git commit -m "$(cat <<'EOF' +feat(dashboard): per-minute RateChart with windows + stacked-area toggle + +bucketRecordsByMinute aligns the current minute as the LAST bucket +(windowMinutes+1 total) so live calls aren't dropped — the legacy +bug, locked down as a regression test. Window selector +(15m/30m/1h/6h), Show all / Disable all when stacked. Reads from the +timeline buffer (no new endpoint). + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 18: `DrillDownPanel.svelte` + click handlers on chart slices + +**Files:** +- Create: `dashboard/src/components/stats/DrillDownPanel.svelte` +- Modify: `dashboard/src/lib/stores/stats.svelte.ts` (add `drillTool` state) +- Modify: `dashboard/src/components/stats/StatsPage.svelte` (wire chart click → setDrillTool) +- Modify: `dashboard/src/components/stats/ChartPanel.svelte` (forward click events with the clicked label) +- Create: `dashboard/tests/drilldown.test.ts` + +- [ ] **Step 1: Write the failing test (p95 computation)** + +Create `dashboard/tests/drilldown.test.ts`: + +```ts +import { describe, it, expect } from 'vitest'; +import { percentile } from '../src/lib/format'; + +describe('percentile', () => { + it('computes p95 over a fixed window', () => { + const xs = Array.from({ length: 100 }, (_, i) => i + 1); // 1..100 + expect(percentile(xs, 95)).toBe(95); + }); + it('returns 0 for empty input', () => { + expect(percentile([], 95)).toBe(0); + }); + it('clamps quantile', () => { + expect(percentile([1, 2, 3], 200)).toBe(3); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd dashboard && npm test -- drilldown` + +Expected: FAIL — `percentile` undefined. + +- [ ] **Step 3: Add `percentile` to `dashboard/src/lib/format.ts`** + +Append: + +```ts +export function percentile(xs: number[], q: number): number { + if (xs.length === 0) return 0; + const sorted = [...xs].sort((a, b) => a - b); + const idx = Math.min(sorted.length - 1, Math.max(0, Math.floor((q / 100) * sorted.length))); + return sorted[idx]; +} +``` + +- [ ] **Step 4: Add `drillTool` state in stats store** + +Edit `dashboard/src/lib/stores/stats.svelte.ts`: + +```ts +// inside createStatsStore: +let drillTool = $state(null); +// add to return: +get drillTool() { + return drillTool; +}, +setDrillTool(t: string | null) { + drillTool = t; +}, +``` + +- [ ] **Step 5: Create `dashboard/src/components/stats/DrillDownPanel.svelte`** + +```svelte + + +{#if tool} + +{/if} + + +``` + +- [ ] **Step 6: Forward click → setDrillTool from `StatsPage.svelte`** + +In `StatsPage.svelte`, where pies / tokens bar / DurationChart are rendered, pass an `onSliceClick` callback (matching `ChartPanel`'s existing event/callback API — read first; if a click prop doesn't exist, add one in `ChartPanel.svelte`): + +```svelte + stats.setDrillTool(label)} +/> + { + timeline.setFilter(tool); + navigate('overview'); // or whatever the existing nav helper is +}} /> +``` + +- [ ] **Step 7: Run tests + check** + +Run: `cd dashboard && npm test -- drilldown && npm run check` + +Expected: PASS. + +- [ ] **Step 8: Commit** + +```bash +git add dashboard/src/components/stats/DrillDownPanel.svelte dashboard/src/components/stats/StatsPage.svelte dashboard/src/components/stats/ChartPanel.svelte dashboard/src/lib/stores/stats.svelte.ts dashboard/src/lib/format.ts dashboard/tests/drilldown.test.ts +git commit -m "$(cat <<'EOF' +feat(dashboard): per-tool DrillDownPanel with p95 + Open in Timeline + +Click any pie / tokens bar / duration chart slice to open a side panel +with aggregates (Calls, Errors with %, Avg, p95, Total, Max), recent +errors, and the last 20 calls. p95 computed client-side from the +timeline buffer; UI hints at the window. 'Open in Timeline' applies +the tool filter and navigates back to Overview. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +## Phase 5 — Backend `/code/*` routes + +> **⚠️ CRITICAL CORRECTION:** All four `/code/*` route tasks (19–23) below use **FastAPI**. The actual dashboard is **Flask**. **Do not implement the original Phase 5 task code — use the rewritten "Corrected Tasks 19–23 (Flask `/code/*` routes)" section near the top of this plan.** Additional corrections that apply: +> +> - **B29:** LSP method names in the original tasks are wrong. Real names: `request_document_symbols(rel_path)` returns `DocumentSymbols` (object with `.root_symbols`, not a list of dicts); `request_workspace_symbol(query)` (SINGULAR, no `limit` param); `request_published_text_document_diagnostics(rel_path)` returns LSP-shape diagnostics. SymbolKind is an integer requiring a label map. +> - **B30:** `manager.get_language_server(".")` raises (it requires a file). Use `next(iter(manager.iter_language_servers()), None)` for "any LS" use cases. +> - **B31:** `manager.started_language_servers_relative_paths()` doesn't exist. Walk the project tree directly. +> - **B32:** Use `GitignoreParser` from `src/serena/util/file_system.py:130` (existing helper) instead of the hardcoded skip set. Honors nested `.gitignore` files. +> - **B33:** Map `TimeoutError` → 504 `ls_timeout` (spec §7.1 requires this; original task collapses everything to 502/503). +> - **B34:** Path-traversal guard needs NUL-byte rejection and `pathlib.Path.resolve().relative_to(...)` instead of `os.path.commonpath` (cleaner on Windows). +> - **B35:** `os.scandir` follows symlinks by default — use `follow_symlinks=False`. +> - **B36:** 1 MB diagnostics cap heuristic is wrong (assumes uniform diag size). Trim per-message first, then loop-pop until under cap. + +> **Implementer note for this whole phase:** all four routes share a path-traversal guard and an LSP error→HTTP translator. Task 19 creates the helpers; Tasks 20–23 use them. Each route lives in `src/serena/dashboard_code.py`, registered from `dashboard.py` via a single `register_code_routes(dashboard_api)` call (Flask). Read `dashboard.py` first to see how it currently wires routes onto `self._app`; the new helper should match that style. + +### Task 19: Path-traversal guard + LSP error helper in `dashboard_code.py` + +**Files:** +- Create: `src/serena/dashboard_code.py` +- Create: `test/serena/test_dashboard_code.py` + +- [ ] **Step 1: Write the failing tests** + +Create `test/serena/test_dashboard_code.py`: + +```python +import pytest + +from serena.dashboard_code import resolve_project_path, LSPNotReady + + +def test_resolve_project_path_rejects_traversal(tmp_path): + root = tmp_path / "proj" + root.mkdir() + (root / "file.py").write_text("x = 1") + # OK + resolved = resolve_project_path(str(root), "file.py") + assert resolved == str((root / "file.py").resolve()) + # Rejects ../ + with pytest.raises(ValueError): + resolve_project_path(str(root), "../secret.txt") + # Rejects absolute path outside root + with pytest.raises(ValueError): + resolve_project_path(str(root), "/etc/passwd") + + +def test_resolve_project_path_rejects_symlink_escape(tmp_path): + import os + root = tmp_path / "proj" + root.mkdir() + outside = tmp_path / "outside.txt" + outside.write_text("bad") + link = root / "link.txt" + os.symlink(str(outside), str(link)) + with pytest.raises(ValueError): + resolve_project_path(str(root), "link.txt") + + +def test_resolve_project_path_rejects_missing_file(tmp_path): + root = tmp_path / "proj" + root.mkdir() + with pytest.raises(FileNotFoundError): + resolve_project_path(str(root), "missing.py") + + +def test_lsp_not_ready_is_an_exception_type(): + assert issubclass(LSPNotReady, Exception) +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `uv run pytest test/serena/test_dashboard_code.py -v` + +Expected: FAIL — module missing. + +- [ ] **Step 3: Create `src/serena/dashboard_code.py`** + +```python +""" +Code-tab endpoints for the dashboard. + +This module encapsulates the four /code/* routes used by the dashboard_v2 Code +tab. It does NOT register a router on its own; call register_code_routes(app, +agent) from dashboard.py. + +Concurrency note: LSP requests serialize at the language-server subprocess's +stdin/stdout pipe. The diagnostics endpoint is slow; the frontend shows a +warning banner and disables Refresh while a request is outstanding. No global +lock is added here. +""" + +from __future__ import annotations + +import logging +import os +from typing import TYPE_CHECKING + +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel + +if TYPE_CHECKING: + from serena.agent import SerenaAgent + +log = logging.getLogger(__name__) + +_FILE_LIMIT_CAP = 2000 +_WORKSPACE_SYMBOL_LIMIT_CAP = 200 +_DIAGNOSTICS_PER_FILE_BYTE_CAP = 1024 * 1024 # 1 MB + + +class LSPNotReady(Exception): + """Raised when an /code/* route is called but no LanguageServer is initialized.""" + + +def resolve_project_path(project_root: str, path: str) -> str: + """ + Resolve `path` (which must be a relative path under the project root) to an + absolute path. Raises ValueError on traversal/escape and FileNotFoundError + on missing files. + + - Absolute paths are rejected (must be relative to project root). + - .. traversal is rejected via commonpath check on the realpath. + - Symlinks pointing outside the root are rejected. + """ + if os.path.isabs(path): + raise ValueError(f"absolute path not allowed: {path!r}") + root_real = os.path.realpath(project_root) + candidate = os.path.realpath(os.path.join(root_real, path)) + if os.path.commonpath([candidate, root_real]) != root_real: + raise ValueError(f"path escapes project root: {path!r}") + if not os.path.exists(candidate): + raise FileNotFoundError(candidate) + return candidate + + +def _get_project_root_or_503(agent: "SerenaAgent") -> str: + project = agent.get_active_project() + if project is None: + raise HTTPException(status_code=503, detail={"error": "No active project", "code": "no_project"}) + return project.project_root + + +def _get_language_server_or_503(agent: "SerenaAgent", path: str | None = None): + """ + Returns a SolidLanguageServer for the given relative path (or any for the + project if path is None). Raises HTTPException 503 with `ls_not_ready` if + no language server is initialized. + """ + try: + manager = agent.get_language_server_manager() + except Exception as e: # noqa: BLE001 + raise HTTPException(status_code=503, detail={"error": str(e), "code": "ls_not_ready"}) + if manager is None: + raise HTTPException(status_code=503, detail={"error": "Language server not ready", "code": "ls_not_ready"}) + try: + if path is not None: + return manager.get_language_server(path) + # No relative path: return any started server (manager exposes one). + return manager.get_language_server(".") + except Exception as e: # noqa: BLE001 + raise HTTPException(status_code=503, detail={"error": str(e), "code": "ls_not_ready"}) + + +def register_code_routes(app: FastAPI, agent: "SerenaAgent") -> None: + """Register /code/* routes onto the given FastAPI app.""" + # Implemented across Tasks 20-23. + pass +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `uv run pytest test/serena/test_dashboard_code.py -v` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/serena/dashboard_code.py test/serena/test_dashboard_code.py +git commit -m "$(cat <<'EOF' +feat(dashboard): scaffold dashboard_code module + path-traversal guard + +New module hosting the /code/* routes. resolve_project_path enforces: +no absolute paths, no .. traversal, no symlink escape, file must +exist. _get_language_server_or_503 normalizes LS readiness errors to +HTTP 503 with code=ls_not_ready. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 20: `/code/list_dir` endpoint + +**Files:** +- Modify: `src/serena/dashboard_code.py` (implement list_dir route inside register_code_routes) +- Modify: `src/serena/dashboard.py` (call register_code_routes) +- Modify: `test/serena/test_dashboard_code.py` + +- [ ] **Step 1: Write the failing tests** + +Append to `test/serena/test_dashboard_code.py`: + +```python +def test_code_list_dir_returns_entries(make_agent_with_project_root): + from fastapi.testclient import TestClient + agent, dashboard, root = make_agent_with_project_root() + (root / "src").mkdir() + (root / "src" / "main.py").write_text("print('hi')") + (root / "README.md").write_text("hi") + client = TestClient(dashboard.app) + + r = client.get("/code/list_dir?path=.") + assert r.status_code == 200 + names = {e["name"] for e in r.json()["entries"]} + assert "src" in names and "README.md" in names + kinds = {e["name"]: e["kind"] for e in r.json()["entries"]} + assert kinds["src"] == "dir" + assert kinds["README.md"] == "file" + + +def test_code_list_dir_rejects_traversal(make_agent_with_project_root): + from fastapi.testclient import TestClient + agent, dashboard, root = make_agent_with_project_root() + client = TestClient(dashboard.app) + r = client.get("/code/list_dir?path=../etc") + assert r.status_code == 400 +``` + +Add the `make_agent_with_project_root` fixture to the file: + +```python +@pytest.fixture +def make_agent_with_project_root(tmp_path, monkeypatch): + from serena.agent import SerenaAgent + from serena.dashboard import SerenaDashboardAPI + + def _factory(): + root = tmp_path / "proj" + root.mkdir() + agent = SerenaAgent.__new__(SerenaAgent) + agent._tool_usage_stats = None + + class _Project: + project_root = str(root) + project_name = "proj" + + monkeypatch.setattr(agent, "get_active_project", lambda: _Project(), raising=False) + monkeypatch.setattr(agent, "get_language_server_manager", lambda: None, raising=False) + dashboard = SerenaDashboardAPI(agent) + return agent, dashboard, root + + return _factory +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `uv run pytest test/serena/test_dashboard_code.py -v` + +Expected: FAIL — 404 from `/code/list_dir`. + +- [ ] **Step 3: Implement the route in `register_code_routes`** + +Replace the `register_code_routes` stub in `src/serena/dashboard_code.py` with: + +```python +class _DirEntry(BaseModel): + name: str + kind: str # "dir" | "file" + size: int | None = None + + +class _ResponseListDir(BaseModel): + entries: list[_DirEntry] + + +_GITIGNORE_HARDCODED = {".git", "node_modules", "__pycache__", ".venv", ".mypy_cache", ".pytest_cache"} + + +def register_code_routes(app: FastAPI, agent: "SerenaAgent") -> None: + @app.get("/code/list_dir") + def code_list_dir(path: str = ".") -> _ResponseListDir: + root = _get_project_root_or_503(agent) + try: + resolved = resolve_project_path(root, path) if path not in ("", ".") else os.path.realpath(root) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except FileNotFoundError: + raise HTTPException(status_code=404, detail="directory not found") + if not os.path.isdir(resolved): + raise HTTPException(status_code=400, detail="path is not a directory") + entries: list[_DirEntry] = [] + try: + with os.scandir(resolved) as it: + for de in it: + if de.name in _GITIGNORE_HARDCODED or de.name.startswith("."): + continue + if de.is_dir(): + entries.append(_DirEntry(name=de.name, kind="dir")) + elif de.is_file(): + try: + size = de.stat().st_size + except OSError: + size = None + entries.append(_DirEntry(name=de.name, kind="file", size=size)) + except PermissionError: + raise HTTPException(status_code=403, detail="permission denied") + entries.sort(key=lambda e: (e.kind == "file", e.name.lower())) + return _ResponseListDir(entries=entries) +``` + +- [ ] **Step 4: Wire `register_code_routes` from `dashboard.py`** + +In `src/serena/dashboard.py`, find where the dashboard initializes routes (likely in `SerenaDashboardAPI.__init__` after the existing `@self.app.get(...)` registrations). Add: + +```python + from serena.dashboard_code import register_code_routes + register_code_routes(self.app, self._agent) +``` + +(Use the attribute the dashboard already uses for the agent — check whether it's `self._agent` or `self.agent`.) + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `uv run pytest test/serena/test_dashboard_code.py -v` + +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add src/serena/dashboard_code.py src/serena/dashboard.py test/serena/test_dashboard_code.py +git commit -m "$(cat <<'EOF' +feat(dashboard): GET /code/list_dir (lazy folder tree) + +One-level directory listing under the active project root, with +path-traversal guard, hardcoded skip-list (.git, node_modules, +__pycache__, .venv, dotfiles), and dir-then-file sort. Foundation for +the Code tab's FileTree. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 21: `/code/file_symbols` endpoint + +**Files:** +- Modify: `src/serena/dashboard_code.py` (add file_symbols route) +- Modify: `test/serena/test_dashboard_code.py` + +- [ ] **Step 1: Write the failing test (with mocked LSP)** + +Append to `test/serena/test_dashboard_code.py`: + +```python +def test_code_file_symbols_returns_lsp_document_symbols(make_agent_with_lsp, tmp_path): + from fastapi.testclient import TestClient + agent, dashboard, root = make_agent_with_lsp( + document_symbols={ + "main.py": [ + { + "name": "Foo", + "kind": "Class", + "range": {"start": {"line": 0, "character": 0}, "end": {"line": 5, "character": 0}}, + "children": [ + { + "name": "bar", + "kind": "Method", + "range": {"start": {"line": 1, "character": 4}, "end": {"line": 3, "character": 0}}, + "children": [], + } + ], + }, + ], + } + ) + (root / "main.py").write_text("class Foo:\n def bar(self): pass\n") + client = TestClient(dashboard.app) + r = client.get("/code/file_symbols?path=main.py") + assert r.status_code == 200 + body = r.json() + assert body["symbols"][0]["name"] == "Foo" + assert body["symbols"][0]["children"][0]["name"] == "bar" + + +def test_code_file_symbols_returns_503_when_no_ls(make_agent_with_project_root): + from fastapi.testclient import TestClient + agent, dashboard, root = make_agent_with_project_root() + (root / "f.py").write_text("x=1") + client = TestClient(dashboard.app) + r = client.get("/code/file_symbols?path=f.py") + assert r.status_code == 503 + assert r.json()["detail"]["code"] == "ls_not_ready" + + +def test_code_file_symbols_404_for_missing(make_agent_with_project_root): + from fastapi.testclient import TestClient + agent, dashboard, root = make_agent_with_project_root() + client = TestClient(dashboard.app) + r = client.get("/code/file_symbols?path=nope.py") + assert r.status_code == 404 +``` + +Add the `make_agent_with_lsp` fixture: + +```python +@pytest.fixture +def make_agent_with_lsp(tmp_path, monkeypatch): + """ + Builds an agent whose language server manager is a fake returning preset + document symbols / workspace matches / diagnostics. + """ + from serena.agent import SerenaAgent + from serena.dashboard import SerenaDashboardAPI + + def _factory(*, document_symbols=None, workspace_matches=None, diagnostics=None): + root = tmp_path / "proj" + root.mkdir() + agent = SerenaAgent.__new__(SerenaAgent) + agent._tool_usage_stats = None + + class _Project: + project_root = str(root) + project_name = "proj" + + class _LS: + def request_document_symbols(self, path): + return (document_symbols or {}).get(os.path.basename(path), []) + + def request_workspace_symbols(self, q, limit): + return (workspace_matches or [])[:limit] + + def request_published_text_document_diagnostics(self, path): + return (diagnostics or {}).get(os.path.basename(path), []) + + class _Manager: + def get_language_server(self, path): + return _LS() + + def started_language_servers_relative_paths(self): + return ["main.py"] + + monkeypatch.setattr(agent, "get_active_project", lambda: _Project(), raising=False) + monkeypatch.setattr(agent, "get_language_server_manager", lambda: _Manager(), raising=False) + dashboard = SerenaDashboardAPI(agent) + return agent, dashboard, root + + return _factory +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `uv run pytest test/serena/test_dashboard_code.py -v -k file_symbols` + +Expected: FAIL. + +- [ ] **Step 3: Add the route to `register_code_routes`** + +Inside `register_code_routes` in `src/serena/dashboard_code.py`, append (with supporting models above `register_code_routes`): + +```python +class _Position(BaseModel): + line: int + character: int + + +class _Range(BaseModel): + start: _Position + end: _Position + + +class _FileSymbol(BaseModel): + name: str + kind: str + range: _Range + children: list["_FileSymbol"] | None = None + + +class _ResponseFileSymbols(BaseModel): + symbols: list[_FileSymbol] +``` + +Inside `register_code_routes` (next to `code_list_dir`): + +```python + @app.get("/code/file_symbols") + def code_file_symbols(path: str) -> _ResponseFileSymbols: + root = _get_project_root_or_503(agent) + try: + resolved = resolve_project_path(root, path) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except FileNotFoundError: + raise HTTPException(status_code=404, detail="file not found") + ls = _get_language_server_or_503(agent, path) + rel = os.path.relpath(resolved, os.path.realpath(root)) + try: + raw = ls.request_document_symbols(rel) + except Exception as e: # noqa: BLE001 + log.error("LSP document symbols failed for %s: %s", rel, e, exc_info=e) + raise HTTPException(status_code=502, detail={"error": str(e), "code": "ls_error"}) + + def _convert(node: dict) -> _FileSymbol: + return _FileSymbol( + name=node["name"], + kind=str(node.get("kind", "")), + range=node["range"], + children=[_convert(c) for c in node.get("children", [])] if node.get("children") else None, + ) + + return _ResponseFileSymbols(symbols=[_convert(s) for s in raw]) +``` + +> **Implementer note:** the exact LSP method name (`request_document_symbols`) and return shape must match what `SolidLanguageServer` actually exposes — read `src/solidlsp/ls.py` if there's a difference. Adjust the call site and the `_convert` mapping accordingly. The fake `_LS` in the fixture should be updated to match the real method signature. + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `uv run pytest test/serena/test_dashboard_code.py -v -k file_symbols` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/serena/dashboard_code.py test/serena/test_dashboard_code.py +git commit -m "$(cat <<'EOF' +feat(dashboard): GET /code/file_symbols (LSP document symbols) + +Returns a nested symbol tree for the requested file. 404 if missing, +503 ls_not_ready when no LS is initialized, 502 ls_error when LSP +raises, 400 on path-escape. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 22: `/code/workspace_symbol_search` endpoint + +**Files:** +- Modify: `src/serena/dashboard_code.py` +- Modify: `test/serena/test_dashboard_code.py` + +- [ ] **Step 1: Write the failing test** + +Append: + +```python +def test_code_workspace_symbol_search_returns_matches(make_agent_with_lsp): + from fastapi.testclient import TestClient + agent, dashboard, root = make_agent_with_lsp( + workspace_matches=[ + {"name": "foo_bar", "kind": "Function", "path": "src/a.py", "range": {"start": {"line": 1, "character": 0}, "end": {"line": 2, "character": 0}}}, + {"name": "foo_baz", "kind": "Function", "path": "src/b.py", "range": {"start": {"line": 3, "character": 0}, "end": {"line": 4, "character": 0}}}, + ] + ) + client = TestClient(dashboard.app) + r = client.get("/code/workspace_symbol_search?q=foo") + assert r.status_code == 200 + body = r.json() + assert len(body["matches"]) == 2 + assert body["matches"][0]["name"] == "foo_bar" + + +def test_code_workspace_symbol_search_limit_capped(make_agent_with_lsp): + from fastapi.testclient import TestClient + agent, dashboard, root = make_agent_with_lsp(workspace_matches=[]) + client = TestClient(dashboard.app) + r = client.get("/code/workspace_symbol_search?q=x&limit=9999") + assert r.status_code == 200 +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `uv run pytest test/serena/test_dashboard_code.py -v -k workspace_symbol` + +Expected: FAIL. + +- [ ] **Step 3: Add the route** + +Add models above `register_code_routes`: + +```python +class _WorkspaceMatch(BaseModel): + name: str + kind: str + path: str + range: _Range + + +class _ResponseWorkspaceSymbolSearch(BaseModel): + matches: list[_WorkspaceMatch] +``` + +Add route inside `register_code_routes`: + +```python + @app.get("/code/workspace_symbol_search") + def code_workspace_symbol_search(q: str, limit: int = 50) -> _ResponseWorkspaceSymbolSearch: + if not q: + return _ResponseWorkspaceSymbolSearch(matches=[]) + limit = max(1, min(limit, _WORKSPACE_SYMBOL_LIMIT_CAP)) + ls = _get_language_server_or_503(agent) + try: + raw = ls.request_workspace_symbols(q, limit) + except Exception as e: # noqa: BLE001 + log.error("LSP workspace symbols failed for %r: %s", q, e, exc_info=e) + raise HTTPException(status_code=502, detail={"error": str(e), "code": "ls_error"}) + return _ResponseWorkspaceSymbolSearch( + matches=[ + _WorkspaceMatch( + name=m["name"], kind=str(m.get("kind", "")), path=m["path"], range=m["range"], + ) + for m in raw + ] + ) +``` + +> **Implementer note:** `SolidLanguageServer` may not currently expose `request_workspace_symbols`. If not, add a thin wrapper in `solidlsp/ls.py` that sends an LSP `workspace/symbol` request — refer to `request_document_symbols` for the request-style pattern already in place. Then update the fake `_LS` in the test fixture accordingly. + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `uv run pytest test/serena/test_dashboard_code.py -v -k workspace_symbol` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/serena/dashboard_code.py src/solidlsp/ls.py test/serena/test_dashboard_code.py +git commit -m "$(cat <<'EOF' +feat(dashboard): GET /code/workspace_symbol_search (LSP workspace/symbol) + +Debounced from the frontend; backend caps limit at 200. Empty q +returns []. Thin solidlsp wrapper added if not already present. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 23: `/code/diagnostics_summary` endpoint + +**Files:** +- Modify: `src/serena/dashboard_code.py` +- Modify: `test/serena/test_dashboard_code.py` + +- [ ] **Step 1: Write the failing tests** + +Append: + +```python +def test_code_diagnostics_summary_returns_files_with_diagnostics(make_agent_with_lsp): + from fastapi.testclient import TestClient + agent, dashboard, root = make_agent_with_lsp( + diagnostics={ + "main.py": [ + {"severity": "error", "message": "undefined name 'x'", "line": 1, "column": 4, "source": "pyflakes"}, + ], + } + ) + (root / "main.py").write_text("print(x)") + client = TestClient(dashboard.app) + r = client.get("/code/diagnostics_summary?file_limit=10") + assert r.status_code == 200 + body = r.json() + assert any(f["path"].endswith("main.py") for f in body["files"]) + assert body["truncated"] is False + + +def test_code_diagnostics_summary_file_limit_capped(make_agent_with_lsp): + from fastapi.testclient import TestClient + agent, dashboard, root = make_agent_with_lsp(diagnostics={}) + client = TestClient(dashboard.app) + r = client.get("/code/diagnostics_summary?file_limit=999999") + assert r.status_code == 200 +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `uv run pytest test/serena/test_dashboard_code.py -v -k diagnostics` + +Expected: FAIL. + +- [ ] **Step 3: Add the route** + +Add models above `register_code_routes`: + +```python +class _Diagnostic(BaseModel): + severity: str + message: str + line: int + column: int + source: str | None = None + + +class _FileDiagnostics(BaseModel): + path: str + diagnostics: list[_Diagnostic] + + +class _ResponseDiagnosticsSummary(BaseModel): + files: list[_FileDiagnostics] + truncated: bool +``` + +Add route inside `register_code_routes`: + +```python + @app.get("/code/diagnostics_summary") + def code_diagnostics_summary(file_limit: int = 1000) -> _ResponseDiagnosticsSummary: + file_limit = max(1, min(file_limit, _FILE_LIMIT_CAP)) + root = _get_project_root_or_503(agent) + ls = _get_language_server_or_503(agent) + # Discover the files that the LS knows about (or fall back to walking). + candidate_paths: list[str] = [] + try: + candidate_paths = list(ls.started_language_servers_relative_paths()) + except Exception: # noqa: BLE001 + for dirpath, dirnames, filenames in os.walk(root): + # prune common skip dirs in place + dirnames[:] = [d for d in dirnames if d not in _GITIGNORE_HARDCODED and not d.startswith(".")] + for fn in filenames: + if fn.startswith("."): + continue + rel = os.path.relpath(os.path.join(dirpath, fn), root) + candidate_paths.append(rel) + truncated = False + if len(candidate_paths) > file_limit: + candidate_paths = candidate_paths[:file_limit] + truncated = True + + files: list[_FileDiagnostics] = [] + total_bytes = 0 + for rel in candidate_paths: + try: + raw = ls.request_published_text_document_diagnostics(rel) + except Exception as e: # noqa: BLE001 + log.warning("Diagnostics failed for %s: %s", rel, e) + continue + if not raw: + continue + diags = [ + _Diagnostic( + severity=str(d.get("severity", "info")), + message=str(d.get("message", "")), + line=int(d.get("line", 0)), + column=int(d.get("column", 0)), + source=d.get("source"), + ) + for d in raw + ] + # Per-file size cap: if encoded JSON would exceed 1 MB, truncate the list. + fd = _FileDiagnostics(path=rel, diagnostics=diags) + payload_size = len(fd.model_dump_json().encode("utf-8")) + if payload_size > _DIAGNOSTICS_PER_FILE_BYTE_CAP: + # Drop diagnostics until under cap (keep oldest indices). + keep = max(1, int(len(diags) * _DIAGNOSTICS_PER_FILE_BYTE_CAP / payload_size)) + fd = _FileDiagnostics(path=rel, diagnostics=diags[:keep]) + truncated = True + files.append(fd) + total_bytes += payload_size + + return _ResponseDiagnosticsSummary(files=files, truncated=truncated) +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `uv run pytest test/serena/test_dashboard_code.py -v -k diagnostics` + +Expected: PASS. + +- [ ] **Step 5: Run the whole backend test suite** + +Run: `uv run pytest test/serena/test_dashboard_code.py test/serena/test_dashboard.py test/serena/test_analytics.py test/serena/test_task_executor.py -v` + +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add src/serena/dashboard_code.py test/serena/test_dashboard_code.py +git commit -m "$(cat <<'EOF' +feat(dashboard): GET /code/diagnostics_summary (1 MB/file cap) + +Iterates known LS-loaded paths (or walks the project), requests +published diagnostics, applies 1 MB per-file payload cap and a +file_limit cap (default 1000, max 2000). Returns truncated=true when +caps fire. Failed per-file diagnostics are logged + skipped, never +fail the whole response. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +## Phase 6 — Frontend Code tab + +> **⚠️ Corrections that apply to Phase 6 (see top of plan for details):** +> - **B37 (CRITICAL):** `$state>(new Set())` in Task 24 is NOT reactive in Svelte 5. The FileTree chevron won't flip on toggle. Use `SvelteSet` from `svelte/reactivity`. The corrected `code` store with `SvelteSet` AND error states is in §"Corrected Task 24" near the top of this plan. +> - **B39:** Task 24's `loadDir` / `loadFileSymbols` have no error states; Spec §7.2 requires them. Corrected store includes `dir_errors` and `file_symbol_errors` records. +> - **B40:** `$components` alias does not exist; use `$lib/*` or relative paths. Synchronous import per existing `App.svelte` pattern. +> - **B41:** Update `Header.svelte` for the Code tab button. +> - **B42:** Task 27's `code.search()` needs `q.trim().length < 2` guard. +> - **B43:** Task 27's `selectMatch` must call `code.selectPath(path, { switchMiddleTo: 'symbols' })` so clicking a search result switches to the Symbols pane (corrected store API supports this). +> - **B44:** Task 28's `diagError = (e as Error).message` fails for non-Error throws. Use `e instanceof Error ? e.message : String(e)`. +> - **B45:** Task 30 Playwright Code-tab tests will see 503 `ls_not_ready` (the emulator has no LSP). Stub `/code/*` via `page.route(...)` in the test setup. + +### Task 24: `code` store + register code tab in App.svelte + +**Files:** +- Create: `dashboard/src/lib/stores/code.svelte.ts` +- Modify: `dashboard/src/App.svelte` (register Code tab between Stats and Logs) +- Create: `dashboard/tests/code-store.test.ts` + +- [ ] **Step 1: Write the failing tests** + +Create `dashboard/tests/code-store.test.ts`: + +```ts +import { describe, it, expect, vi } from 'vitest'; +import { stubFetchRoutes } from './helpers'; +import { createCodeStore } from '../src/lib/stores/code.svelte'; + +describe('code store', () => { + it('caches list_dir results per path', async () => { + const calls: string[] = []; + stubFetchRoutes([ + { + match: '/code/list_dir', + handler: (url) => { + calls.push(url); + return { entries: [{ name: 'a', kind: 'file' }] }; + }, + }, + ]); + const store = createCodeStore(); + await store.loadDir('src'); + await store.loadDir('src'); // cached + expect(calls.length).toBe(1); + }); + + it('caches file_symbols results per path', async () => { + const calls: string[] = []; + stubFetchRoutes([ + { + match: '/code/file_symbols', + handler: (url) => { + calls.push(url); + return { symbols: [] }; + }, + }, + ]); + const store = createCodeStore(); + await store.loadFileSymbols('a.py'); + await store.loadFileSymbols('a.py'); + expect(calls.length).toBe(1); + }); + + it('search uses epoch counter to discard stale responses', async () => { + let resolveSlow: (v: unknown) => void = () => {}; + const slow = new Promise((res) => (resolveSlow = res)); + let queries: string[] = []; + stubFetchRoutes([ + { + match: '/code/workspace_symbol_search', + handler: async (url) => { + queries.push(url); + if (url.includes('q=ab')) { + await slow; + return { matches: [{ name: 'OLD', kind: 'F', path: 'x.py', range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }] }; + } + return { matches: [{ name: 'NEW', kind: 'F', path: 'y.py', range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } }] }; + }, + }, + ]); + const store = createCodeStore(); + const p1 = store.search('ab'); + const p2 = store.search('abc'); + await p2; + resolveSlow(null); + await p1; + expect(store.search_results[0]?.name).toBe('NEW'); + }); +}); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cd dashboard && npm test -- code-store` + +Expected: FAIL — module missing. + +- [ ] **Step 3: Create `dashboard/src/lib/stores/code.svelte.ts`** + +```ts +import { + fetchCodeListDir, + fetchCodeFileSymbols, + fetchCodeWorkspaceSymbolSearch, + fetchCodeDiagnosticsSummary, +} from '$lib/api/endpoints'; +import type { DirEntry, FileSymbol, WorkspaceMatch, FileDiagnostics } from '$lib/api/types'; + +export function createCodeStore() { + const dirChildren = $state>({}); + const expanded = $state>(new Set()); + let selectedPath = $state(null); + const fileSymbols = $state>({}); + let searchQuery = $state(''); + let searchResults = $state([]); + let searchLoading = $state(false); + let searchEpoch = 0; + let diagFiles = $state([]); + let diagLoading = $state(false); + let diagError = $state(null); + let diagTruncated = $state(false); + let diagLastRefreshAt = $state(null); + + return { + get dir_children() { + return dirChildren; + }, + get expanded() { + return expanded; + }, + get selected_path() { + return selectedPath; + }, + get file_symbols() { + return fileSymbols; + }, + get search_query() { + return searchQuery; + }, + get search_results() { + return searchResults; + }, + get search_loading() { + return searchLoading; + }, + get diag_files() { + return diagFiles; + }, + get diag_loading() { + return diagLoading; + }, + get diag_error() { + return diagError; + }, + get diag_truncated() { + return diagTruncated; + }, + get diag_last_refresh_at() { + return diagLastRefreshAt; + }, + async loadDir(path: string) { + if (dirChildren[path]) return; + const resp = await fetchCodeListDir(path); + dirChildren[path] = resp.entries; + }, + toggleExpand(path: string) { + if (expanded.has(path)) expanded.delete(path); + else { + expanded.add(path); + void this.loadDir(path); + } + }, + selectPath(path: string | null) { + selectedPath = path; + if (path) void this.loadFileSymbols(path); + }, + async loadFileSymbols(path: string) { + if (fileSymbols[path]) return; + const resp = await fetchCodeFileSymbols(path); + fileSymbols[path] = resp.symbols; + }, + async search(q: string) { + searchQuery = q; + if (!q.trim()) { + searchResults = []; + searchLoading = false; + return; + } + const myEpoch = ++searchEpoch; + searchLoading = true; + try { + const resp = await fetchCodeWorkspaceSymbolSearch(q, 50); + if (myEpoch === searchEpoch) { + searchResults = resp.matches; + } + } finally { + if (myEpoch === searchEpoch) searchLoading = false; + } + }, + async refreshDiagnostics(file_limit = 1000) { + diagLoading = true; + diagError = null; + try { + const resp = await fetchCodeDiagnosticsSummary(file_limit); + diagFiles = resp.files; + diagTruncated = resp.truncated; + diagLastRefreshAt = Date.now(); + } catch (e) { + diagError = (e as Error).message; + } finally { + diagLoading = false; + } + }, + }; +} + +export const code = createCodeStore(); +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `cd dashboard && npm test -- code-store` + +Expected: PASS. + +- [ ] **Step 5: Register the Code tab in `App.svelte`** + +Read `App.svelte` first. Find the tab list (the switch on `view`) and add `'code'` between Stats and Logs. Example shape: + +```svelte + + +{#if view === 'code'} + {#await import('$components/code/CodePage.svelte') then m} + + {/await} +{/if} +``` + +> **Implementer note:** match the existing structural pattern — the snippet above is a reference. If the project doesn't currently lazy-load views, render synchronously instead. + +- [ ] **Step 6: Type-check + run store tests** + +Run: `cd dashboard && npm run check && npm test -- code-store` + +Expected: PASS. + +- [ ] **Step 7: Commit** + +```bash +git add dashboard/src/lib/stores/code.svelte.ts dashboard/src/App.svelte dashboard/tests/code-store.test.ts +git commit -m "$(cat <<'EOF' +feat(dashboard): code store + Code tab between Stats and Logs + +createCodeStore() owns lazy folder cache, file-symbols cache, search +state with epoch-counter staleness gating, and diagnostics state. +Code tab registered in the App.svelte tab list and pollers map. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 25: `FileTree.svelte` (lazy folder tree) + +**Files:** +- Create: `dashboard/src/components/code/FileTree.svelte` + +- [ ] **Step 1: Create the component** + +```svelte + + +{#snippet treeNode(path: string, depth: number)} + {@const children = code.dir_children[path]} +
                + {#if !children} +
              • + {:else} + {#each children as entry} + {@const fullPath = joinPath(path, entry.name)} +
              • + {#if entry.kind === 'dir'} + + {#if code.expanded.has(fullPath)} + {@render treeNode(fullPath, depth + 1)} + {/if} + {:else} + + {/if} +
              • + {/each} + {/if} +
              +{/snippet} + +{@render treeNode(rootPath, 0)} + + +``` + +- [ ] **Step 2: Commit** + +```bash +git add dashboard/src/components/code/FileTree.svelte +git commit -m "$(cat <<'EOF' +feat(dashboard): FileTree (lazy folder navigation for Code tab) + +Click folder to expand/collapse; loads on first expand and caches. +Click file to select (drives FileSymbols pane). + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 26: `FileSymbols.svelte` (LSP document symbols) + +**Files:** +- Create: `dashboard/src/components/code/FileSymbols.svelte` + +- [ ] **Step 1: Create the component** + +```svelte + + +{#snippet symbolNode(s: FileSymbol, depth: number)} +
            • + {s.kind} + {s.name} + {s.range.start.line + 1}:{s.range.start.character + 1} +
            • + {#if s.children && s.children.length > 0} + {#each s.children as c} + {@render symbolNode(c, depth + 1)} + {/each} + {/if} +{/snippet} + +
              + {#if !path} +

              Select a file from the tree.

              + {:else if symbols === undefined} +

              Loading…

              + {:else if symbols.length === 0} +

              No symbols.

              + {:else} +
                + {#each symbols as s} + {@render symbolNode(s, 0)} + {/each} +
              + {/if} +
              + + +``` + +- [ ] **Step 2: Commit** + +```bash +git add dashboard/src/components/code/FileSymbols.svelte +git commit -m "$(cat <<'EOF' +feat(dashboard): FileSymbols (nested LSP document-symbols pane) + +Renders the symbol tree for the file selected in FileTree. Loading +and empty states. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 27: `WorkspaceSearch.svelte` (debounced + epoch staleness) + +**Files:** +- Create: `dashboard/src/components/code/WorkspaceSearch.svelte` + +- [ ] **Step 1: Create the component** + +```svelte + + +
              + + {#if code.search_loading} +
              Searching…
              + {:else if value && code.search_results.length === 0} +
              No matches.
              + {:else if code.search_results.length > 0} +
                + {#each code.search_results as m} +
              • + +
              • + {/each} +
              + {/if} +
              + + +``` + +- [ ] **Step 2: Commit** + +```bash +git add dashboard/src/components/code/WorkspaceSearch.svelte +git commit -m "$(cat <<'EOF' +feat(dashboard): WorkspaceSearch (LSP workspace/symbol) + +Debounced 250 ms input → /code/workspace_symbol_search. Click a +match → selects the file in the tree. Store-level epoch counter +prevents stale slow-search responses from overwriting newer ones. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 28: `DiagnosticsPanel.svelte` (with warning banner, no cancel) + +**Files:** +- Create: `dashboard/src/components/code/DiagnosticsPanel.svelte` + +- [ ] **Step 1: Create the component** + +```svelte + + +
              +
              +

              Diagnostics

              + +
              + + {#if code.diag_loading} +
              + ⚠ Computing diagnostics — this can take a while and temporarily slows other + LSP-backed tools. +
              + {/if} + + {#if code.diag_error} +
              + Diagnostics failed. +
              {code.diag_error}
              +
              + {/if} + + {#if code.diag_truncated && !code.diag_loading} +
              + Showing first {code.diag_files.length} files; project has more. +
              + {/if} + + {#if code.diag_files.length === 0 && !code.diag_loading && !code.diag_error} +

              + Click Refresh to compute diagnostics. (Results are not auto-refreshed because + diagnostics is slow.) +

              + {/if} + + {#each code.diag_files as f} +
              + + {f.path} + {f.diagnostics.length} + +
                + {#each f.diagnostics as d} +
              • + {d.line + 1}:{d.column + 1} + {d.severity} + {d.message} + {#if d.source}[{d.source}]{/if} +
              • + {/each} +
              +
              + {/each} +
              + + +``` + +- [ ] **Step 2: Commit** + +```bash +git add dashboard/src/components/code/DiagnosticsPanel.svelte +git commit -m "$(cat <<'EOF' +feat(dashboard): DiagnosticsPanel (manual refresh + warning, no cancel) + +Refresh button disabled while in-flight; warning banner explains that +computing diagnostics temporarily slows other LSP-backed tools. +Previous results stay visible on error. Files-with-issues grouped by +path; expandable to individual diagnostics (severity, line:col, +message, source). + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 29: `CodePage.svelte` three-pane layout + +**Files:** +- Create: `dashboard/src/components/code/CodePage.svelte` + +- [ ] **Step 1: Create the component** + +```svelte + + +
              + +
              + + {#if middle === 'symbols'} + + {:else} + + {/if} +
              + +
              + + +``` + +- [ ] **Step 2: Verify the Code tab renders end-to-end** + +Run: `cd dashboard && npm run check` + +Expected: no type errors. + +Manual: `cd dashboard && npm run dev` with a running Serena MCP backend (one that has a language server). Click Code tab; confirm three panes render; expand a folder, click a file, see symbols; type in the workspace search box; click Refresh in Diagnostics and see warning banner + result. + +- [ ] **Step 3: Commit** + +```bash +git add dashboard/src/components/code/CodePage.svelte +git commit -m "$(cat <<'EOF' +feat(dashboard): CodePage three-pane layout (Tree / Symbols+Search / Diagnostics) + +Composes FileTree, FileSymbols ⇄ WorkspaceSearch, DiagnosticsPanel. +Middle pane tab switches Symbols/Search without losing tree state. +Responsive: stacks vertically on narrow viewports. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +## Phase 7 — Final pass (smoke test, format, asset rebuild) + +### Task 30: Playwright golden-path smoke test + +**Files:** +- Create: `dashboard/tests/code-tab.spec.ts` (Playwright) +- Modify: `dashboard/package.json` if Playwright isn't installed yet + +> **Prereq:** the emulator script (`run-dashboard-emulate-tool-calls.md` in memory) seeds synthetic tool calls so the dashboard has data without a live agent loop. Use it as the boot mechanism. + +- [ ] **Step 1: Confirm Playwright availability** + +Run: `cd dashboard && npx playwright --version || echo "missing"` + +If `missing`: `cd dashboard && npm install -D @playwright/test && npx playwright install chromium`. Add a `playwright.config.ts` if none exists, pointing `baseURL` at `http://localhost:5273`. + +- [ ] **Step 2: Write the smoke test** + +Create `dashboard/tests/code-tab.spec.ts`: + +```ts +import { test, expect } from '@playwright/test'; + +// Goal: golden-path validation of every new surface. Requires the dashboard +// running (e.g. via the emulator script) on localhost:5273 before this test +// is invoked. + +test('overview shows 4 summary cards', async ({ page }) => { + await page.goto('/'); + await page.getByRole('button', { name: 'Overview' }).click(); + const titles = await page.locator('[data-card-title]').allTextContents(); + expect(titles).toEqual(['Calls', 'Tokens', 'Time', 'Errors']); +}); + +test('timeline rows render and expand', async ({ page }) => { + await page.goto('/'); + await page.getByRole('button', { name: 'Overview' }).click(); + const firstRow = page.locator('[data-timeline-row]').first(); + await expect(firstRow).toBeVisible({ timeout: 10_000 }); + await firstRow.getByRole('button').first().click(); + await expect(page.getByText('seq:')).toBeVisible(); +}); + +test('stats sort selector reorders charts', async ({ page }) => { + await page.goto('/'); + await page.getByRole('button', { name: 'Stats' }).click(); + // Capture the first chart's category labels with default sort + const before = await page.locator('canvas').first().evaluate(() => 'ok'); + await page.getByLabel(/Sort by/i).selectOption({ value: 'duration_total' }); + // Charts update in place — visual change isn't easy to assert here, so just + // confirm no errors and that the selector reflects the change. + await expect(page.getByLabel(/Sort by/i)).toHaveValue('duration_total'); + expect(before).toBe('ok'); +}); + +test('clicking a pie slice opens drill-down panel', async ({ page }) => { + await page.goto('/'); + await page.getByRole('button', { name: 'Stats' }).click(); + // Click in the center of the first chart canvas — actual slice hit is best-effort. + const canvas = page.locator('canvas').first(); + const box = await canvas.boundingBox(); + if (!box) throw new Error('canvas has no bounding box'); + await page.mouse.click(box.x + box.width * 0.4, box.y + box.height * 0.4); + await expect(page.getByRole('dialog', { name: 'Tool details' })).toBeVisible({ timeout: 3_000 }); +}); + +test('code tab navigates folder tree and shows symbols', async ({ page }) => { + await page.goto('/'); + await page.getByRole('button', { name: 'Code' }).click(); + // Wait for any folder row to appear + const firstDir = page.locator('button.row.dir').first(); + await expect(firstDir).toBeVisible({ timeout: 10_000 }); + await firstDir.click(); + // Click the first file under it + const firstFile = page.locator('button.row.file').first(); + await firstFile.click(); + // Symbols pane should show "Loading…" or actual symbols quickly + await expect(page.getByText(/Loading|Symbols/)).toBeVisible({ timeout: 5_000 }); +}); + +test('diagnostics refresh shows warning banner', async ({ page }) => { + await page.goto('/'); + await page.getByRole('button', { name: 'Code' }).click(); + const refresh = page.getByRole('button', { name: /Refresh/ }); + await refresh.click(); + await expect(page.getByText(/Computing diagnostics/)).toBeVisible({ timeout: 3_000 }); +}); +``` + +- [ ] **Step 3: Run the smoke test** + +Start the emulator + dashboard per `run-dashboard-emulate-tool-calls.md`, then in another shell: + +`cd dashboard && npx playwright test code-tab.spec.ts` + +Expected: PASS (6 tests). + +- [ ] **Step 4: Commit** + +```bash +git add dashboard/tests/code-tab.spec.ts dashboard/playwright.config.ts dashboard/package.json dashboard/package-lock.json +git commit -m "$(cat <<'EOF' +test(dashboard): Playwright golden-path smoke for the port + +Single end-to-end spec covering the 6 new surfaces: SummaryCards, +Timeline row expansion, SortSelector, DrillDown, Code tab navigation, +Diagnostics warning banner. Run against the emulator script before +shipping. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +--- + +### Task 31: Format + run all tests + regenerate built assets + +This is the CI contract: any Svelte change requires a rebuilt `src/serena/resources/dashboard/` checked in. + +- [ ] **Step 1: Run the full frontend test suite** + +Run: `cd dashboard && npm run format && npm test` + +Expected: PASS, no formatting changes after. + +- [ ] **Step 2: Run the full backend test suite** + +Run: `uv run pytest test/serena/ -q` + +Expected: PASS. + +- [ ] **Step 3: Type-check** + +Run: `cd dashboard && npm run check` + +Expected: PASS. + +- [ ] **Step 4: Rebuild assets** + +Run: `cd dashboard && npm run build` + +Expected: `../src/serena/resources/dashboard/index.html` and `../src/serena/resources/dashboard/assets/` regenerated. + +- [ ] **Step 5: Stage everything and commit** + +```bash +git add src/serena/resources/dashboard/ +git status # confirm only the regenerated assets show +git commit -m "$(cat <<'EOF' +build(dashboard): regenerate bundle for legacy-feature port + +Final asset rebuild after the Timeline / SummaryCards / Sort / +Duration / Rate / DrillDown / Code-tab additions. + +Co-Authored-By: Claude Opus 4.7 +EOF +)" +``` + +- [ ] **Step 6: Manual verification with the emulator** + +Follow `~/.claude/projects/-Users-Pavlo-Basanets-PycharmProjects-serena-fork/memory/run-dashboard-emulate-tool-calls.md`. Open the dashboard in a browser. Verify, with console open: + +- 4 SummaryCards under Overview, updating each second. +- Timeline ticks in new rows; expand one; see args + output; truncation note appears if seeded with >8 KB I/O. +- Pause stops new rows; Resume picks them back up; Clear empties the view. +- Per-tool filter applies via FilterDropdown; × clears it. +- Stats tab: SortSelector changes reorder pies + tokens bar + DurationChart consistently. +- RateChart shows per-minute counts; window selector switches; stacked-area toggle reveals per-tool layers; Show all / Disable all work. +- Click a pie slice → DrillDownPanel slides in with the 6 aggregates + p95 + recent errors + last 20 calls + "Open in Timeline" jump. +- Code tab between Stats and Logs. FileTree expands lazily; click a file → FileSymbols renders; toggle "Search (workspace)" → debounced search works. +- DiagnosticsPanel: Refresh button shows the warning banner; result renders; previous results stay on error. +- Open DevTools → no console errors during any of the above. + +If anything fails, fix in a follow-up task and rebuild assets. + +--- + +## Self-review (run after the plan above is complete) + +This section is meant for the implementer to run against this plan + the spec — it's the same checklist used when authoring. + +**1. Spec coverage check.** Walk through every numbered section of `docs/superpowers/specs/2026-05-28-dashboard-v2-legacy-port-design.md` and confirm a task implements it: + +| Spec section | Task(s) | +|---|---| +| §3 Architecture & module boundaries | All phases | +| §4.1 `analytics.py` extensions | T1, T2, T3 | +| §4.2 `task_executor.py` enrichments + race fix | T4 | +| §4.3 Tool dispatch instrumentation | T5 | +| §4.4 New endpoints (timeline + code + tool_stats_totals + executions) | T6, T7, T8, T20, T21, T22, T23 | +| §4.5 Path-traversal guard | T19 | +| §5.1 FilterDropdown | T11 | +| §5.2 SummaryCards + Timeline | T12, T13, T14 | +| §5.3 SortSelector + DurationChart + RateChart + DrillDownPanel | T15, T16, T17, T18 | +| §5.4 Code tab (FileTree / FileSymbols / WorkspaceSearch / DiagnosticsPanel) | T24, T25, T26, T27, T28, T29 | +| §5.5 Polling primitive `??` | T10 (baked into store + regression test) | +| §6 Data flow & polling, timeline buffer cap, backpressure | T10, T14 | +| §7 Error handling | T5 (analytics safety), T19 (HTTP code translation), T28 (UI on error) | +| §8.1 Backend tests | T1, T2, T3, T4, T6, T7, T8, T19, T20, T21, T22, T23 | +| §8.2 Frontend tests | T10, T11, T12, T13, T15, T16, T17, T18, T24 | +| §8.3 Playwright smoke | T30 | +| §9 Manual verification | T31 | +| §10 Implementation sequence | Matches Phases 1–7 | + +**2. Placeholder scan.** Search this plan for "TBD", "TODO", "fill in", "similar to" — none should appear. The "Implementer note:" callouts are intentional pointers; they direct the implementer to *read* a referenced file before editing, not placeholders. + +**3. Type consistency.** + +- `ToolCallRecord` shape used identically in backend (`analytics.py`), backend Pydantic (`ToolCallRecordResponse`), and frontend (`types.ts`). +- `ToolStatsTotals` keys (`num_calls`, `num_errors`, `total_duration_ms`, `total_tokens`) are identical in backend dict (T7), Pydantic field (T7), and frontend `types.ts` (T9). +- `SortKey` union (`'calls' | 'tokens' | 'duration_total' | 'duration_avg' | 'errors'`) is identical in `stats.svelte.ts` and `SortSelector.svelte` and `sortToolsBy`. +- Diagnostic severity strings (`'error' | 'warning' | 'info' | 'hint'`) match between backend `_Diagnostic.severity` (a `str` field, validated implicitly by source) and frontend `DiagnosticSeverity`. The backend currently passes through whatever the LS sends — the implementer should normalize to these four values in T23 if the LSP returns numeric LSP severity codes (1=error, 2=warning, 3=info, 4=hint). + +**4. Scope check.** Single coherent port: agent observability + project navigator. All tasks share the same Svelte runes + pytest patterns. 7 phases, 31 tasks; each task is one PR-worthy unit. + +--- + +## Execution Handoff + +**Plan complete and saved to `docs/superpowers/plans/2026-05-28-dashboard-v2-legacy-port.md`. Two execution options:** + +**1. Subagent-Driven (recommended)** — I dispatch a fresh subagent per task, review between tasks, fast iteration. + +**2. Inline Execution** — Execute tasks in this session using executing-plans, batch execution with checkpoints. + +**Which approach?** + diff --git a/docs/superpowers/specs/2026-05-28-dashboard-v2-legacy-port-design.md b/docs/superpowers/specs/2026-05-28-dashboard-v2-legacy-port-design.md new file mode 100644 index 000000000..bcf55f61f --- /dev/null +++ b/docs/superpowers/specs/2026-05-28-dashboard-v2-legacy-port-design.md @@ -0,0 +1,417 @@ +# Dashboard v2 — Port of legacy `dashboard` branch features + +**Date:** 2026-05-28 +**Branch:** `dashboard_v2` +**Scope:** Tiers 1, 2, 4, 5 from the feature audit, plus Tier 3 #9 (Rate chart) and #11 (Avg/Max duration chart). LSP Diagnostics gets a warning banner instead of a cancel button. + +--- + +## 1. Goal + +Port the agent-observability features from the legacy jQuery `dashboard` branch into the Svelte 5 + TypeScript `dashboard_v2` rebuild. The legacy branch added per-call timing, an in-memory record buffer, a live tool-call Timeline, a Code tab with project navigator and LSP diagnostics, and several UX refinements. None of these exist in `dashboard_v2` today. + +This spec defines a single coherent port, scoped to one implementation plan. It is **not** a full re-implementation of the legacy branch: we deliberately omit co-occurrence (Tier 3 #10), the dev-only injection endpoint, the editor-URL template, and an LSP cancel mechanism. + +## 2. Non-goals + +- Implementing LSP `$/cancelRequest` in `solidlsp`. Out of scope; we surface a warning instead. +- Tool sequences / co-occurrence chart (Tier 3 #10). +- "Open in external editor" URL template (Tier 5 polish). +- Restoring `/_dev_inject_tool_call`. The local emulator script covers this. +- Any global LSP serialization lock. Behavior matches today; we only make existing serialization visible to the user. + +## 3. Architecture & module boundaries + +### 3.1 Backend (Python) + +Three existing modules touched, one new helper: + +- `src/serena/analytics.py` — extend `ToolUsageStats.Entry`; add `ToolCallRecord` and a bounded ring buffer with a monotonic seq cursor on `ToolUsageStats`. +- `src/serena/task_executor.py` — extend `TaskInfo`; race-fix the ordering around `Future` resolution. +- `src/serena/agent.py` — wrap tool dispatch with timing + error capture via a `_record_tool_call_safely` helper. +- `src/serena/dashboard.py` — five new routes; one extended payload (`/get_config_overview`). +- `src/serena/dashboard_code.py` — **new** helper module encapsulating the four `/code/*` endpoints, so `dashboard.py` stays a thin router. + +### 3.2 Frontend (Svelte 5 + TS) + +Layout under `dashboard/src/`: + +- **Stores** (new): `lib/stores/timeline.svelte.ts`, `lib/stores/code.svelte.ts`. +- **API client / types** (extend): `lib/api/endpoints.ts`, `lib/api/types.ts`. +- **Polling registration** (extend): `lib/pollers.ts` — add `'code'` view, register `timeline` poller on both `'overview'` and `'stats'`. +- **Polling primitive fix**: switch cursor coalescing from `||` to `??` in the shared poller helper. +- **Components** (new): + - `components/overview/SummaryCards.svelte` (replaces the inline KPI strip in `StatsSummary.svelte`). + - `components/overview/Timeline.svelte` + `TimelineRow.svelte`. + - `components/stats/SortSelector.svelte`. + - `components/stats/DurationChart.svelte`. + - `components/stats/RateChart.svelte`. + - `components/stats/DrillDownPanel.svelte`. + - `components/code/CodePage.svelte` + `FileTree.svelte` + `FileSymbols.svelte` + `WorkspaceSearch.svelte` + `DiagnosticsPanel.svelte`. + - `components/common/FilterDropdown.svelte` — shared filter primitive. + +### 3.3 Tab order + +`Overview / Stats / Code / Logs`. Code goes between Stats and Logs. + +### 3.4 Build contract + +Existing: any Svelte change requires `npm run build` and committing the regenerated assets under `src/serena/resources/dashboard/`. + +## 4. Backend changes + +### 4.1 `analytics.py` + +**Extended `Entry`:** + +```python +@dataclass(kw_only=True) +class Entry: + num_times_called: int = 0 + num_errors: int = 0 # new + input_tokens: int = 0 + output_tokens: int = 0 + total_duration_ms: float = 0.0 # new + min_duration_ms: float | None = None # new + max_duration_ms: float | None = None # new + last_called_at: float | None = None # new (epoch s) + + def update_on_call( + self, input_tokens: int, output_tokens: int, + duration_ms: float, success: bool, now: float, + ) -> None: ... +``` + +**New `ToolCallRecord`:** + +```python +@dataclass(frozen=True) +class ToolCallRecord: + seq: int + tool: str + started_at: float + duration_ms: float + success: bool + error_message: str | None + input_preview: str # truncated to 8 KB + output_preview: str # truncated to 8 KB + input_truncated: bool + output_truncated: bool +``` + +**`ToolUsageStats` additions:** + +- `_records: collections.deque[ToolCallRecord]` with `maxlen=2000`. +- `_seq_counter: int` (monotonic, never reused). +- One `threading.Lock` guarding `_entries`, `_records`, `_seq_counter` together. +- `record_call(tool, input_str, output_str, duration_ms, success, error)` — updates `Entry` and appends a `ToolCallRecord` atomically. +- `get_records_since(since_seq: int | None, tool: str | None, limit: int) -> tuple[list[ToolCallRecord], int]` — returns records and current `max_seq`. `since_seq=None` returns the tail up to `limit`. + +**Constants:** `_INPUT_OUTPUT_PREVIEW_BYTES = 8 * 1024`, `_RECORD_BUFFER_SIZE = 2000`. + +### 4.2 `task_executor.py` + +**Extended `TaskInfo`:** + +```python +@dataclass +class TaskInfo: + name: str + is_running: bool + future: Future + task_id: int + logged: bool + started_at: float | None = None # new + finished_at: float | None = None # new + + def get_duration_ms(self) -> int | None: ... + def get_error_message(self) -> str | None: ... + def get_display_name(self) -> str: # strips "Task-N: " prefix +``` + +**Race fix** in `Task.start()`'s `run_task` closure — `finished_at` is set **before** the future is resolved: + +```python +self.task_info.started_at = time.time() +try: + result = fn(*args, **kwargs) + self.task_info.finished_at = time.time() + self.future.set_result(result) +except BaseException as e: + self.task_info.finished_at = time.time() + self.future.set_exception(e) +``` + +`get_error_message()` reads from `future.exception()` only after `finished_at` is set. Never raises. + +### 4.3 Tool dispatch instrumentation (`agent.py`) + +Wrap `tool.apply()` in `agent.py` (the layer that knows the tool name and input/output reprs). A `try/finally` block calls `_record_tool_call_safely`, which catches `BaseException` from the analytics call and logs at WARNING. Instrumentation must never break the agent. + +```python +start = time.perf_counter() +success = True +error_message = None +result = "" +try: + result = tool.apply(...) + return result +except BaseException as e: + success = False + error_message = type(e).__name__ + ": " + str(e) + raise +finally: + duration_ms = (time.perf_counter() - start) * 1000 + self._record_tool_call_safely( + tool_name, input_repr, result, duration_ms, success, error_message, + ) +``` + +### 4.4 New / extended endpoints + +| Route | Purpose | Notes | +|---|---|---| +| `GET /get_tool_call_timeline?since_seq=&tool=&limit=` | Tier 2 timeline | `limit` capped at 500 | +| `GET /code/list_dir?path=` | Lazy folder tree | One level only; respects gitignore | +| `GET /code/file_symbols?path=` | LSP document symbols | Nested tree | +| `GET /code/workspace_symbol_search?q=&limit=` | LSP workspace/symbol | `limit` capped at 200, default 50 | +| `GET /code/diagnostics_summary?file_limit=` | Project-wide diagnostics | 1 MB/file cap, `file_limit` default 1000 capped at 2000, returns `truncated` flag | + +**`/get_config_overview` extended:** adds `tool_stats_totals = {num_calls, num_errors, total_duration_ms, total_tokens}` to drive the 4-card summary on the existing 1 s poll. + +**`/queued_task_executions` and `/last_execution` extended:** include `duration_ms`, `error_message`, `display_name`, `started_at`, `finished_at`. + +**Diagnostics warning behavior.** No cancel button. The Diagnostics panel surfaces a banner when a refresh is in flight: *"Computing diagnostics — this can take a while and temporarily slows other LSP-backed tools."* Refresh button disabled while a request is outstanding. + +### 4.5 Path-traversal guard + +`/code/list_dir` and `/code/file_symbols` resolve `path` via `os.path.realpath` and assert `os.path.commonpath([resolved, project_root]) == project_root`. Any escape (relative `..`, absolute path, symlink to outside) → 400. + +## 5. Frontend changes + +### 5.1 Shared primitive: `FilterDropdown.svelte` + +Reusable filtered single-select. Props: `options: {value, label}[]`, `value: string | null`, `placeholder`, `onChange`. Behavior: + +- Clear (×) button when a value is set. +- Active-state border in the existing orange accent token. +- Checkmark beside the applied option when the dropdown reopens. +- Keyboard nav: opening focuses the applied option; ↑/↓ moves; Enter selects; Esc closes. +- Substring filter typed while focused. +- Click-outside closes. + +Used by Timeline's per-tool filter and any future filtered list. + +### 5.2 Overview tab + +**`SummaryCards.svelte`** — four cards driven by extended `/get_config_overview`: + +| Card | Main | Subtext | +|---|---|---| +| Calls | `num_calls` (thousands-separated) | error rate `%` | +| Tokens | `total` (k/M formatted) | `in / out` split | +| Time | `total_duration_ms` formatted (`1.2 s`, `45 m`) | `avg per call` | +| Errors | `num_errors` | last error tool name (if any) | + +`StatsSummary.svelte` is kept but trimmed — the inline strip extracts to `SummaryCards`; other Overview-summary content stays. + +**`Timeline.svelte` + `TimelineRow.svelte`** — sits below `SummaryCards`. + +Layout: header with per-tool filter (FilterDropdown), pause/resume, clear-view; page-size selector and pagination (first / prev / next / last). Rows expand inline to: `seq`, full ISO timestamp, `duration_ms`, input/output token counts, full input args and output. Long values get a "Show full" toggle; truncation notice when `input_truncated`/`output_truncated` is true. + +### 5.3 Stats tab + +**`SortSelector.svelte`** at the top of the Stats toolbar. Options: Calls (default), Tokens (total), Total duration, Avg duration, Errors. Single store key drives all chart specs — pies, tokens bar, and the new duration chart reorder consistently. + +**`DurationChart.svelte`** — Chart.js chart, two datasets per tool: avg (solid) and max (overlay outline). Reuses the existing spec-builder pattern. + +**`RateChart.svelte`** — line chart on a per-minute time axis. Controls: window selector (15 m / 30 m / 1 h / 6 h) and a per-tool stacked-area toggle with Show all / Disable all buttons. Data is computed client-side from the timeline buffer (no new endpoint). Bucket alignment: current minute is bucket N; render N+1 buckets so the live minute isn't dropped. + +> **Constraint:** with a 2000-record buffer, the 6 h window only shows whatever fraction of those 6 h is present in the buffer (high-rate sessions will see less than the full window). Matches legacy behavior; we don't add a server endpoint to backfill. Acceptable for an in-memory observability view. + +**`DrillDownPanel.svelte`** — right-slide overlay (300–360 px wide), opens on click of any pie slice / tokens bar / duration chart bar. Content: aggregates (Calls, Errors with %, Avg, p95, Total, Max), Recent errors, Last 20 calls, "Open in Timeline" button. p95 computed client-side from the timeline buffer for that tool, with a "based on last 2000 calls" hint shown near p95. + +### 5.4 Code tab + +Three-pane layout: FileTree | (FileSymbols ⇄ WorkspaceSearch) | DiagnosticsPanel. + +- **`FileTree.svelte`** — lazy folder tree. Click folder → `/code/list_dir`. Click file → updates `code` store's `selectedPath`. +- **`FileSymbols.svelte`** — shows `/code/file_symbols` for the selected file. Nested LSP symbol tree with kind icons. +- **`WorkspaceSearch.svelte`** — search box at the top of the middle pane. Debounced 250 ms. Hits `/code/workspace_symbol_search`. Result rows click-to-jump (selects file + scrolls symbols). Toggle in middle-pane header switches between "Symbols (file)" and "Search (workspace)". +- **`DiagnosticsPanel.svelte`** — independent of selection. Refresh button + warning banner. Files-with-issues grouped by path; expandable to individual diagnostics (severity, message, line:col). Refresh disabled while in flight; previous results stay visible on error. + +**Store** `lib/stores/code.svelte.ts`: + +```ts +{ + expanded: Set, + children: Map, + selectedPath: string | null, + fileSymbols: Map, + search: { q: string, results: Match[], loading: boolean, epoch: number }, + diagnostics: { files: …, loading: boolean, error: string | null, lastRefreshAt: number | null }, +} +``` + +**No polling on the Code tab** — everything is on-demand. Older in-flight searches are discarded via a `searchEpoch` compare-and-set. + +### 5.5 Polling primitive change + +In the shared poller helper: `since = response.max_seq ?? since` (was `||`). One-line change, one unit test. + +## 6. Data flow & polling + +### 6.1 Polling topology + +| Poll loop | View(s) | Cadence | Cursor | Pauses when | +|---|---|---|---|---| +| `config` (existing) | Overview, Stats, Code | 1 s | none | tab hidden | +| `executions` (existing) | Overview | 1 s | none | tab hidden | +| `logs` (existing) | Logs | 1 s | byte offset | tab hidden | +| `timeline` (new) | Overview, Stats | 1 s | `since_seq` | tab hidden OR user paused OR view ≠ Overview/Stats | +| `code` (new) | — | none | — | always (on-demand only) | + +Timeline polls on Stats too so RateChart, DurationChart, and DrillDown stay current. + +### 6.2 Timeline buffer is the frontend source of truth + +`lib/stores/timeline.svelte.ts` owns a single in-memory list capped at 2000 records (mirroring backend). Consumers: `Timeline`, `RateChart`, `DurationChart`, `DrillDownPanel`. For tools whose history has scrolled out of the 2000-record window, aggregated `/get_tool_stats` continues to drive the pies/bars. + +### 6.3 Request lifecycles + +**Tool call → record.** Synchronous, on the agent thread. Lock held for microseconds. Errors in `_record_tool_call_safely` are caught, logged, and dropped. + +**Timeline poll.** `GET /get_tool_call_timeline?since_seq=N&tool=T&limit=200`. Server: lock-protected scan, filter, slice. Client: merge into buffer, cap at 2000, advance cursor with `??`. On long pause / resume, the next response returns the tail and we accept the gap — a "(N calls while paused — view truncated)" hint appears in the Timeline header. + +**Code tab.** Every action user-triggered. Cached in store on success; search uses an epoch counter to discard stale responses. + +### 6.4 Backend concurrency + +We add **no global LSP lock**. LSP requests already serialize at the language-server subprocess's stdin/stdout. Two consequences worth naming: + +- Diagnostics blocks navigator + search while running. Hence the warning banner. +- Each `/code/*` route issues one LSP request and returns — no nested LSP calls, no new deadlock risk. + +`ToolUsageStats`'s lock is independent of everything else, held only for record-buffer mutations. + +### 6.5 Backpressure & sanity caps + +- `_RECORD_BUFFER_SIZE = 2000` (ring) — bounded memory. +- `_INPUT_OUTPUT_PREVIEW_BYTES = 8 * 1024` per record. Worst-case buffer ≈ 32 MB. +- `/get_tool_call_timeline?limit=` capped server-side at 500. +- `/code/diagnostics_summary` per-file payload capped at 1 MB; `file_limit` capped at 2000. +- `/code/workspace_symbol_search?limit=` capped at 200. +- Frontend timeline buffer capped at 2000. + +### 6.6 Visibility-aware polling + +Single `document.visibilityState === 'visible'` gate in the shared poller. When hidden, `timeline` and `config` pause; on resume, they fire immediately, get the gap, and resume cadence. + +## 7. Error handling + +### 7.1 Backend + +| Failure | HTTP | Notes | +|---|---|---| +| LS not initialized | 503 `{"error": "...", "code": "ls_not_ready"}` | Frontend retries on next tick | +| LS timeout | 504 `{"code": "ls_timeout"}` | Existing solidlsp timeout | +| LS responded with error | 502 `{"code": "ls_error"}` | Surface message to user | +| Path traversal / invalid | 400 | | +| Missing / unreadable file | 404 | | +| Unexpected | 500 `{"code": "internal"}` | No stack traces in body; log at ERROR | + +`_record_tool_call_safely` is the one place that wraps `BaseException` and swallows — analytics must never break the agent. `TaskInfo.get_error_message()` returns `None` until `future.done()`; never raises. + +### 7.2 Frontend + +**Polling errors.** + +- 503 `ls_not_ready` → inline "Language server starting…", retries on next tick, no toast. +- Network / 5xx → set `error`, retry with backoff 1 s → 2 s → 5 s (capped). Inline error in affected panel, tab stays up. +- 4xx → set `error`, stop polling for that resource. + +**Timeline poll failure.** Buffer left intact; small banner *"Live updates paused — reconnecting…"* at the top of `Timeline.svelte`. Pause/resume still allows manual retry. + +**Code tab.** + +- `list_dir` fail → folder stays collapsed, inline `⚠ `. +- `file_symbols` fail → middle pane error card with Retry. +- `workspace_symbol_search` fail → older successful results stay visible. +- `diagnostics_summary` fail → error card; previous results preserved below. + +**Drill-down panel.** If `tool` no longer in `/get_tool_stats`, panel shows "No data for this tool" and a Close button. + +**SummaryCards.** If `tool_stats_totals` is missing from `/get_config_overview` (older backend), cards render with em-dashes. + +## 8. Testing + +### 8.1 Backend (pytest) + +**`tests/serena/test_analytics.py`** (new): + +- `Entry.update_on_call` updates duration/error/last_called fields. +- `ToolCallRecord` truncation honors 8 KB cap; `*_truncated` flags set. +- Ring buffer drops oldest at capacity; `_seq` is monotonic, never reused. +- `get_records_since(since_seq=N)` returns only `seq > N`. +- `get_records_since(tool="X")` filters correctly. +- Two threads × 1000 calls each → contiguous seq, correct totals. + +**`tests/serena/test_task_executor.py`** (extend): + +- `started_at`/`finished_at` set in the right order around `set_result`. +- `get_duration_ms` to within tolerance. +- `get_error_message` returns exception string for failed; `None` for successful. +- **Race-fix regression test:** spawn 100 tasks, observe each via `add_done_callback`, assert `finished_at is not None` in every observation. This is the test that would have failed before the fix. + +**`tests/serena/test_dashboard.py`** (extend): + +- `/get_tool_call_timeline` shape; cursor; limit; tool filter. +- `/code/list_dir` path-traversal guard rejects `../`, absolute paths, symlinks outside root. +- `/code/file_symbols` 404 for missing file. +- `/code/workspace_symbol_search` shape; limit cap. +- `/code/diagnostics_summary` truncates large responses; `truncated` flag. +- All `/code/*` return 503 `ls_not_ready` when no LSP is initialized (mocked). + +**Instrumentation integration test:** drive a fake successful tool through the agent → one `ToolCallRecord` with `duration_ms > 0`, `success: true`. Drive a failing tool → `success: false`, `error_message` populated. + +### 8.2 Frontend (vitest) + +- `lib/stores/timeline.svelte.ts`: poll merges, dedups on `seq`, respects buffer cap, advances cursor with `??`. +- `lib/stores/code.svelte.ts`: lazy-load cache; refresh invalidates only the requested key. +- `RateChart` spec builder: bucket alignment puts the current minute as bucket N, with N+1 total buckets (legacy bug regression). +- `DurationChart` spec builder: avg vs max datasets correctly mapped. +- `DrillDownPanel`: p95 against a fixed fixture of 100 records. +- `FilterDropdown`: kbd nav opens on applied value; Esc closes; Enter selects; substring filter applies. +- `SortSelector`-driven specs: changing sort key reorders all three pie specs and the duration chart consistently. + +### 8.3 Playwright smoke test (one, end-to-end) + +Using the emulated-tool-calls dashboard (per memory `run-dashboard-emulate-tool-calls.md`): + +1. 4 SummaryCards render with non-empty values. +2. Timeline shows rows; expand one; see args/output. +3. Switch to Stats; change SortSelector; charts reorder. +4. Click a pie slice; DrillDownPanel opens with stats. +5. Switch to Code tab; expand a folder; click a file; see symbols. +6. Click Refresh in DiagnosticsPanel; warning banner appears; eventual result renders. + +### 8.4 Out of scope for testing + +- Build pipeline (existing CI contract handles asset regeneration). +- Actual LSP server correctness — mocked via a small fake `LanguageServerManager` in dashboard tests. + +## 9. Manual verification before "done" + +Run the emulator script, exercise every new control in the browser, watch for console errors. Type-check + tests pass + assets regenerated before claiming complete. + +## 10. Implementation sequence + +The plan should land in this order so each step is independently verifiable: + +1. **Backend instrumentation** — `Entry` extension, `ToolCallRecord`, `ToolUsageStats` ring buffer, `_record_tool_call_safely` wrap, `TaskInfo` enrichments, race fix. Unblocks all UI. +2. **Backend Tier 2 routes** — `/get_tool_call_timeline`, `/get_config_overview` extension. +3. **Frontend Tier 2** — `SummaryCards`, `FilterDropdown`, `Timeline` + `TimelineRow`, `timeline` store, polling-primitive `??` fix. +4. **Frontend Tier 3 + Stats** — `SortSelector`, `DurationChart`, `RateChart`, `DrillDownPanel`. +5. **Backend Tier 4 routes** — `/code/list_dir`, `/code/file_symbols`, `/code/workspace_symbol_search`, `/code/diagnostics_summary` + `dashboard_code.py`. +6. **Frontend Tier 4** — Code tab, `code` store, all four Code components. +7. **Final pass** — Playwright smoke, asset regeneration, manual emulator verification. diff --git a/src/serena/agent.py b/src/serena/agent.py index 4fe0d2ab9..9871623ff 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -8,6 +8,7 @@ import platform import signal import threading +import time from collections import defaultdict from collections.abc import Callable, Iterator, Sequence from contextlib import contextmanager @@ -836,15 +837,34 @@ def _check_shell_settings(self) -> None: os.environ["COMSPEC"] = "" # force use of default shell log.info("Adjusting COMSPEC environment variable to use the default shell instead of '%s'", comspec) - def record_tool_usage(self, input_kwargs: dict, tool_result: str | dict, tool: Tool) -> None: - """ - Record the usage of a tool with the given input and output strings if tool usage statistics recording is enabled. - """ - tool_name = tool.get_name() - input_str = str(input_kwargs) - output_str = str(tool_result) - log.debug(f"Recording tool usage for tool '{tool_name}'") - self._tool_usage_stats.record_tool_usage(tool_name, input_str, output_str) + def _record_tool_call_safely( + self, + tool_name: str, + input_str: str, + output_str: str, + duration_ms: float, + success: bool, + error_message: str | None, + ) -> None: + """ + Record a tool call into the in-memory analytics buffer. Instrumentation must + never break the agent — any exception from the analytics layer is caught + and logged at WARNING. + """ + if self._tool_usage_stats is None: + return + try: + self._tool_usage_stats.record_call( + tool_name=tool_name, + input_str=input_str, + output_str=output_str, + duration_ms=duration_ms, + success=success, + error_message=error_message, + now=time.time(), + ) + except Exception as e: # noqa: BLE001 — instrumentation MUST NOT break agent + log.warning(f"Failed to record tool call analytics for '{tool_name}': {e}", exc_info=e) def get_dashboard_url(self) -> str | None: """ diff --git a/src/serena/analytics.py b/src/serena/analytics.py index add6fd093..cc34ef492 100644 --- a/src/serena/analytics.py +++ b/src/serena/analytics.py @@ -2,8 +2,9 @@ import logging import threading +import time from abc import ABC, abstractmethod -from collections import defaultdict +from collections import defaultdict, deque from copy import copy from dataclasses import asdict, dataclass from enum import Enum @@ -116,6 +117,36 @@ def load_estimator(self) -> TokenCountEstimator: return estimator_instance +_INPUT_OUTPUT_PREVIEW_BYTES = 8 * 1024 +_RECORD_BUFFER_SIZE = 2000 + + +def _truncate_preview(text: str) -> tuple[str, bool]: + """ + Truncate text so its UTF-8 byte length is at most _INPUT_OUTPUT_PREVIEW_BYTES. + Returns (possibly-truncated text, was_truncated). + """ + encoded = text.encode("utf-8") + if len(encoded) <= _INPUT_OUTPUT_PREVIEW_BYTES: + return text, False + truncated = encoded[:_INPUT_OUTPUT_PREVIEW_BYTES].decode("utf-8", errors="ignore") + return truncated, True + + +@dataclass(frozen=True) +class ToolCallRecord: + seq: int + tool: str + started_at: float + duration_ms: float + success: bool + error_message: str | None + input_preview: str + output_preview: str + input_truncated: bool + output_truncated: bool + + class ToolUsageStats: """ A class to record and manage tool usage statistics. @@ -126,6 +157,8 @@ def __init__(self, token_count_estimator: RegisteredTokenCountEstimator = Regist self._token_estimator_name = token_count_estimator.value self._tool_stats: dict[str, ToolUsageStats.Entry] = defaultdict(ToolUsageStats.Entry) self._tool_stats_lock = threading.Lock() + self._records: deque[ToolCallRecord] = deque(maxlen=_RECORD_BUFFER_SIZE) + self._seq_counter: int = 0 @property def token_estimator_name(self) -> str: @@ -137,16 +170,38 @@ def token_estimator_name(self) -> str: @dataclass(kw_only=True) class Entry: num_times_called: int = 0 + num_errors: int = 0 input_tokens: int = 0 output_tokens: int = 0 - - def update_on_call(self, input_tokens: int, output_tokens: int) -> None: + total_duration_ms: float = 0.0 + min_duration_ms: float | None = None + max_duration_ms: float | None = None + last_called_at: float | None = None + + def update_on_call( + self, + input_tokens: int, + output_tokens: int, + duration_ms: float, + success: bool, + now: float, + ) -> None: """ - Update the entry with the number of tokens used for a single call. + Update the entry for a single call: tokens, duration, success/error, timestamp. """ self.num_times_called += 1 + if not success: + self.num_errors += 1 self.input_tokens += input_tokens self.output_tokens += output_tokens + self.total_duration_ms += duration_ms + self.min_duration_ms = ( + duration_ms if self.min_duration_ms is None else min(self.min_duration_ms, duration_ms) + ) + self.max_duration_ms = ( + duration_ms if self.max_duration_ms is None else max(self.max_duration_ms, duration_ms) + ) + self.last_called_at = now def _estimate_token_count(self, text: str) -> int: return self._token_count_estimator.estimate_token_count(text) @@ -158,17 +213,78 @@ def get_stats(self, tool_name: str) -> ToolUsageStats.Entry: with self._tool_stats_lock: return copy(self._tool_stats[tool_name]) - def record_tool_usage(self, tool_name: str, input_str: str, output_str: str) -> None: + def record_call( + self, + tool_name: str, + input_str: str, + output_str: str, + duration_ms: float, + success: bool, + error_message: str | None, + now: float, + ) -> None: + """ + Record a tool call: updates the aggregate Entry AND appends a ToolCallRecord + to the bounded ring buffer. Atomic under the stats lock. + """ input_tokens = self._estimate_token_count(input_str) output_tokens = self._estimate_token_count(output_str) + input_preview, input_truncated = _truncate_preview(input_str) + output_preview, output_truncated = _truncate_preview(output_str) with self._tool_stats_lock: - entry = self._tool_stats[tool_name] - entry.update_on_call(input_tokens, output_tokens) - - def get_tool_stats_dict(self) -> dict[str, dict[str, int]]: + self._seq_counter += 1 + seq = self._seq_counter + self._tool_stats[tool_name].update_on_call( + input_tokens=input_tokens, + output_tokens=output_tokens, + duration_ms=duration_ms, + success=success, + now=now, + ) + self._records.append( + ToolCallRecord( + seq=seq, + tool=tool_name, + started_at=now - duration_ms / 1000.0, + duration_ms=duration_ms, + success=success, + error_message=error_message, + input_preview=input_preview, + output_preview=output_preview, + input_truncated=input_truncated, + output_truncated=output_truncated, + ) + ) + + def get_records_since( + self, + since_seq: int | None, + tool: str | None, + limit: int, + ) -> tuple[list[ToolCallRecord], int]: + """ + Return (records, max_seq). since_seq=None returns the tail; otherwise only + records with seq > since_seq are returned. Optional per-tool filter. + Result is capped at `limit` (newest preferred via tail-slicing). + """ + limit = max(0, min(limit, _RECORD_BUFFER_SIZE)) + with self._tool_stats_lock: + max_seq = self._seq_counter + snapshot = list(self._records) + if since_seq is not None: + snapshot = [r for r in snapshot if r.seq > since_seq] + if tool is not None: + snapshot = [r for r in snapshot if r.tool == tool] + if len(snapshot) > limit: + snapshot = snapshot[-limit:] + return snapshot, max_seq + + def get_tool_stats_dict(self) -> dict[str, dict[str, float | int | None]]: with self._tool_stats_lock: return {name: asdict(entry) for name, entry in self._tool_stats.items()} def clear(self) -> None: with self._tool_stats_lock: self._tool_stats.clear() + self._records.clear() + self._seq_counter = 0 diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py index f7c5c3ccc..e7128b36e 100644 --- a/src/serena/dashboard.py +++ b/src/serena/dashboard.py @@ -17,7 +17,7 @@ import psutil from flask import Flask, Response, redirect, request, send_from_directory from PIL import Image -from pydantic import BaseModel +from pydantic import BaseModel, Field from sensai.util import logging from sensai.util.pickle import dump_pickle, load_pickle @@ -52,7 +52,7 @@ class ResponseToolNames(BaseModel): class ResponseToolStats(BaseModel): - stats: dict[str, dict[str, int]] + stats: dict[str, dict[str, float | int | None]] class ResponseConfigOverview(BaseModel): @@ -60,7 +60,10 @@ class ResponseConfigOverview(BaseModel): context: dict[str, str] modes: list[dict[str, str]] active_tools: list[str] - tool_stats_summary: dict[str, dict[str, int]] + tool_stats_summary: dict[str, dict[str, float | int | None]] + tool_stats_totals: dict[str, float] = Field( + default_factory=lambda: {"num_calls": 0, "num_errors": 0, "total_duration_ms": 0.0, "total_tokens": 0} + ) registered_projects: list[dict[str, str | bool]] available_tools: list[dict[str, str | bool]] available_modes: list[dict[str, str | bool]] @@ -124,8 +127,13 @@ class QueuedExecution(BaseModel): task_id: int is_running: bool name: str + display_name: str finished_successfully: bool logged: bool + started_at: float | None = None + finished_at: float | None = None + duration_ms: int | None = None + error_message: str | None = None @classmethod def from_task_info(cls, task_info: TaskExecutor.TaskInfo) -> Self: @@ -133,11 +141,34 @@ def from_task_info(cls, task_info: TaskExecutor.TaskInfo) -> Self: task_id=task_info.task_id, is_running=task_info.is_running, name=task_info.name, + display_name=task_info.get_display_name(), finished_successfully=task_info.finished_successfully(), logged=task_info.logged, + started_at=task_info.started_at, + finished_at=task_info.finished_at, + duration_ms=task_info.get_duration_ms(), + error_message=task_info.get_error_message(), ) +class ToolCallRecordResponse(BaseModel): + seq: int + tool: str + started_at: float + duration_ms: float + success: bool + error_message: str | None + input_preview: str + output_preview: str + input_truncated: bool + output_truncated: bool + + +class ResponseToolCallTimeline(BaseModel): + records: list[ToolCallRecordResponse] + max_seq: int + + class ReadNews: def __init__(self, read_ids: list[str], legacy_last_read_id: str | None = None): self._read_ids = set(read_ids) @@ -260,6 +291,58 @@ def get_tool_stats_route() -> dict[str, Any]: result = self._get_tool_stats() return result.model_dump() + @self._app.route("/get_tool_call_timeline", methods=["GET"]) + def get_tool_call_timeline_route() -> tuple[dict[str, Any], int] | dict[str, Any]: + # Flask's type=int returns None for both "absent" and "present but non-numeric". + # Disambiguate by inspecting the raw value first so garbage input 400s instead + # of silently returning the full tail. + since_seq_str = request.args.get("since_seq", default=None, type=str) + if since_seq_str is not None and since_seq_str != "": + try: + since_seq_raw: int | None = int(since_seq_str) + except ValueError: + return {"status": "error", "message": "since_seq must be an integer"}, 400 + else: + since_seq_raw = None + limit_str = request.args.get("limit", default=None, type=str) + if limit_str is not None and limit_str != "": + try: + limit = int(limit_str) + except ValueError: + return {"status": "error", "message": "limit must be an integer"}, 400 + else: + limit = 200 + tool = request.args.get("tool", default=None, type=str) or None # treat "" as None + if since_seq_raw is not None and since_seq_raw < 0: + return {"status": "error", "message": "since_seq must be >= 0"}, 400 + if limit < 0: + return {"status": "error", "message": "limit must be >= 0"}, 400 + if self._tool_usage_stats is None: + return ResponseToolCallTimeline(records=[], max_seq=0).model_dump() + records, max_seq = self._tool_usage_stats.get_records_since( + since_seq=since_seq_raw, + tool=tool, + limit=limit, + ) + return ResponseToolCallTimeline( + records=[ + ToolCallRecordResponse( + seq=r.seq, + tool=r.tool, + started_at=r.started_at, + duration_ms=r.duration_ms, + success=r.success, + error_message=r.error_message, + input_preview=r.input_preview, + output_preview=r.output_preview, + input_truncated=r.input_truncated, + output_truncated=r.output_truncated, + ) + for r in records + ], + max_seq=max_seq, + ).model_dump() + @self._app.route("/clear_tool_stats", methods=["POST"]) def clear_tool_stats_route() -> dict[str, str]: self._clear_tool_stats() @@ -454,6 +537,11 @@ def mark_news_snippet_as_read() -> dict[str, str]: except Exception as e: return {"status": "error", "message": str(e)} + # Register /code/* routes (file tree, symbols, workspace search, diagnostics). + from serena.dashboard_code import register_code_routes + + register_code_routes(self) + def _get_log_messages(self, request_log: RequestLog) -> ResponseLog: messages = self._memory_log_handler.get_log_messages(from_idx=request_log.start_idx) project = self._agent.get_active_project() @@ -562,11 +650,24 @@ def _get_config_overview(self) -> ResponseConfigOverview: } ) + # Snapshot tool stats once — reused for both summary and totals to avoid TOCTOU. + full_stats = self._tool_usage_stats.get_tool_stats_dict() if self._tool_usage_stats is not None else {} + # Get basic tool stats (just num_calls for overview) - tool_stats_summary = {} - if self._tool_usage_stats is not None: - full_stats = self._tool_usage_stats.get_tool_stats_dict() - tool_stats_summary = {name: {"num_calls": stats["num_times_called"]} for name, stats in full_stats.items()} + tool_stats_summary = {name: {"num_calls": stats["num_times_called"]} for name, stats in full_stats.items()} + + # Aggregate totals across all tools (drives the Overview SummaryCards strip). + tool_stats_totals: dict[str, float] = { + "num_calls": 0, + "num_errors": 0, + "total_duration_ms": 0.0, + "total_tokens": 0, + } + for s in full_stats.values(): + tool_stats_totals["num_calls"] += s.get("num_times_called", 0) or 0 + tool_stats_totals["num_errors"] += s.get("num_errors", 0) or 0 + tool_stats_totals["total_duration_ms"] += s.get("total_duration_ms", 0.0) or 0.0 + tool_stats_totals["total_tokens"] += (s.get("input_tokens", 0) or 0) + (s.get("output_tokens", 0) or 0) # Get available memories if ReadMemoryTool is active available_memories = None @@ -589,6 +690,7 @@ def _get_config_overview(self) -> ResponseConfigOverview: modes=modes_info, active_tools=active_tools, tool_stats_summary=tool_stats_summary, + tool_stats_totals=tool_stats_totals, registered_projects=registered_projects, available_tools=available_tools, available_modes=available_modes, diff --git a/src/serena/dashboard_code.py b/src/serena/dashboard_code.py new file mode 100644 index 000000000..d00fa65da --- /dev/null +++ b/src/serena/dashboard_code.py @@ -0,0 +1,491 @@ +""" +Code-tab endpoints for the dashboard. + +Encapsulates the four /code/* routes. Call register_code_routes(dashboard_api) +from SerenaDashboardAPI._setup_routes after the existing route block. + +Concurrency note: LSP requests serialize at the language-server subprocess. +Diagnostics is slow; the frontend shows a warning banner and disables Refresh +while a request is outstanding. No global lock is added here. +""" +from __future__ import annotations + +import logging +import os +import time +from pathlib import Path +from typing import TYPE_CHECKING, Any +from urllib.parse import unquote, urlparse + +from flask import request +from pydantic import BaseModel + +if TYPE_CHECKING: + from serena.dashboard import SerenaDashboardAPI + +log = logging.getLogger(__name__) + +_FILE_LIMIT_CAP = 2000 +_WORKSPACE_SYMBOL_LIMIT_CAP = 200 +_DIAGNOSTICS_PER_FILE_BYTE_CAP = 1024 * 1024 # 1 MB +_DIAGNOSTICS_PER_MESSAGE_CAP = 4096 # 4 KB per diag message +_DIAGNOSTICS_WALL_CLOCK_BUDGET_S = 30.0 # overall deadline for the per-file LSP loop +_SYMBOL_TREE_MAX_DEPTH = 64 # guard against malformed/circular LSP DocumentSymbol trees + + +# LSP SymbolKind → human-readable label. +_LSP_SYMBOL_KIND_LABELS: dict[int, str] = { + 1: "File", 2: "Module", 3: "Namespace", 4: "Package", + 5: "Class", 6: "Method", 7: "Property", 8: "Field", + 9: "Constructor", 10: "Enum", 11: "Interface", 12: "Function", + 13: "Variable", 14: "Constant", 15: "String", 16: "Number", + 17: "Boolean", 18: "Array", 19: "Object", 20: "Key", + 21: "Null", 22: "EnumMember", 23: "Struct", 24: "Event", + 25: "Operator", 26: "TypeParameter", +} + +# LSP DiagnosticSeverity int → label. +_LSP_SEVERITY: dict[int, str] = {1: "error", 2: "warning", 3: "info", 4: "hint"} + + +class LSPNotReady(Exception): + """Raised when an /code/* route is called but no LanguageServer is initialized.""" + + +def resolve_project_path(project_root: str, path: str) -> str: + """ + Resolve `path` (relative to project root) to an absolute path. Raises: + - ValueError on traversal/escape, NUL bytes, or absolute paths + - FileNotFoundError if the resolved path doesn't exist + """ + if "\x00" in path: + raise ValueError(f"path contains NUL byte: {path!r}") + if os.path.isabs(path): + raise ValueError(f"absolute path not allowed: {path!r}") + root_real = Path(project_root).resolve() + candidate = (root_real / path).resolve() + try: + candidate.relative_to(root_real) + except ValueError as e: + raise ValueError(f"path escapes project root: {path!r}") from e + if not candidate.exists(): + raise FileNotFoundError(str(candidate)) + return str(candidate) + + +def _err(status: int, message: str, code: str | None = None) -> tuple[dict[str, Any], int]: + body: dict[str, Any] = {"status": "error", "message": message} + if code is not None: + body["code"] = code + return body, status + + +def _get_project_root(dashboard_api: "SerenaDashboardAPI") -> str | None: + project = dashboard_api._agent.get_active_project() + if project is None: + return None + return str(project.project_root) + + +def _get_first_language_server(dashboard_api: "SerenaDashboardAPI") -> Any | None: + """ + Return the first started language server, or None if none. + Uses ls_manager.iter_language_servers(). + """ + try: + manager = dashboard_api._agent.get_language_server_manager() + except Exception as e: # noqa: BLE001 + log.debug("LS manager unavailable: %s", e) + return None + if manager is None: + return None + try: + for ls in manager.iter_language_servers(): + return ls + except Exception as e: # noqa: BLE001 + log.debug("LS iter failed: %s", e) + return None + + +def _get_language_server_for_path(dashboard_api: "SerenaDashboardAPI", rel_path: str) -> Any | None: + """Get the LS responsible for a specific relative path, or None. + + Catches ValueError (raised by ls_manager.get_language_server when the path is + a directory) and any other Exception as a defensive boundary. + """ + try: + manager = dashboard_api._agent.get_language_server_manager() + except Exception as e: # noqa: BLE001 + log.debug("LS manager unavailable: %s", e) + return None + if manager is None: + return None + try: + return manager.get_language_server(rel_path) + except ValueError: + # The path looked like a directory or otherwise didn't match an LS. + return None + except Exception as e: # noqa: BLE001 + log.debug("get_language_server(%r) failed: %s", rel_path, e) + return None + + +# --------------------------------------------------------------------------- # +# Models # +# --------------------------------------------------------------------------- # + + +class _DirEntry(BaseModel): + name: str + kind: str # "dir" | "file" + size: int | None = None + + +class _ResponseListDir(BaseModel): + entries: list[_DirEntry] + + +class _Position(BaseModel): + line: int + character: int + + +class _Range(BaseModel): + start: _Position + end: _Position + + +class _FileSymbol(BaseModel): + name: str + kind: str + range: _Range + children: list["_FileSymbol"] | None = None + + +class _ResponseFileSymbols(BaseModel): + symbols: list[_FileSymbol] + + +class _WorkspaceMatch(BaseModel): + name: str + kind: str + path: str + range: _Range + + +class _ResponseWorkspaceSymbolSearch(BaseModel): + matches: list[_WorkspaceMatch] + + +class _Diagnostic(BaseModel): + severity: str # "error" | "warning" | "info" | "hint" + message: str + line: int + column: int + source: str | None = None + + +class _FileDiagnostics(BaseModel): + path: str + diagnostics: list[_Diagnostic] + + +class _ResponseDiagnosticsSummary(BaseModel): + files: list[_FileDiagnostics] + truncated: bool + + +_FileSymbol.model_rebuild() + + +# --------------------------------------------------------------------------- # +# LSP shape adapters # +# --------------------------------------------------------------------------- # + + +def _maybe(obj: Any, key: str) -> Any: + """Read key from dict-or-object. Returns None when missing.""" + if obj is None: + return None + if isinstance(obj, dict): + return obj.get(key) + return getattr(obj, key, None) + + +def _convert_lsp_symbol(s: Any, _depth: int = 0) -> _FileSymbol: + """Convert a UnifiedSymbolInformation / LSP DocumentSymbol to dashboard shape. + + Recursion is capped at _SYMBOL_TREE_MAX_DEPTH to guard against malformed or + circular LSP responses (would otherwise stack-overflow on a bad server). + """ + name = _maybe(s, "name") or "?" + kind_int = _maybe(s, "kind") + try: + kind_label = _LSP_SYMBOL_KIND_LABELS.get(int(kind_int) if kind_int is not None else 0, str(kind_int)) + except (TypeError, ValueError): + kind_label = str(kind_int) + # range may be at top-level (DocumentSymbol) or under .location.range (SymbolInformation). + rng = _maybe(s, "range") + if rng is None: + loc = _maybe(s, "location") + rng = _maybe(loc, "range") if loc is not None else None + rng = rng or {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}} + children = _maybe(s, "children") if _depth < _SYMBOL_TREE_MAX_DEPTH else None + return _FileSymbol( + name=name, + kind=kind_label, + range=rng, + children=[_convert_lsp_symbol(c, _depth + 1) for c in children] if children else None, + ) + + +def _convert_workspace_match(m: Any, project_root_real: Path) -> _WorkspaceMatch: + name = _maybe(m, "name") or "?" + kind_int = _maybe(m, "kind") + try: + kind_label = _LSP_SYMBOL_KIND_LABELS.get(int(kind_int) if kind_int is not None else 0, str(kind_int)) + except (TypeError, ValueError): + kind_label = str(kind_int) + location = _maybe(m, "location") + uri = _maybe(location, "uri") if location is not None else None + rng = _maybe(location, "range") if location is not None else None + file_path = unquote(urlparse(uri).path) if uri else "" + try: + rel = str(Path(file_path).relative_to(project_root_real)) if file_path else "" + except Exception: + rel = file_path + return _WorkspaceMatch( + name=name, + kind=kind_label, + path=rel, + range=rng or {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}, + ) + + +# --------------------------------------------------------------------------- # +# Route registration # +# --------------------------------------------------------------------------- # + + +def register_code_routes(dashboard_api: "SerenaDashboardAPI") -> None: + """Register /code/* routes onto the dashboard's Flask app. Called from _setup_routes.""" + app = dashboard_api._app + + # ----- /code/list_dir ----------------------------------------------------- + @app.route("/code/list_dir", methods=["GET"]) + def code_list_dir() -> tuple[dict[str, Any], int] | dict[str, Any]: + path = request.args.get("path", default=".", type=str) or "." + root = _get_project_root(dashboard_api) + if root is None: + return _err(503, "No active project", "no_project") + try: + if path in ("", "."): + resolved = str(Path(root).resolve()) + else: + resolved = resolve_project_path(root, path) + except ValueError as e: + return _err(400, str(e)) + except FileNotFoundError: + return _err(404, "directory not found") + if not os.path.isdir(resolved): + return _err(400, "path is not a directory") + + from serena.util.file_system import GitignoreParser + + try: + ignore: GitignoreParser | None = GitignoreParser(root) + except Exception: + ignore = None + + entries: list[_DirEntry] = [] + try: + with os.scandir(resolved) as it: + for de in it: + if de.name.startswith("."): + continue + is_dir = de.is_dir(follow_symlinks=False) + is_file = de.is_file(follow_symlinks=False) + abs_path = de.path + rel = os.path.relpath(abs_path, root) + if ignore is not None: + # GitignoreParser.should_ignore handles directory/file disambiguation + # internally by appending '/' for directories. + if ignore.should_ignore(rel): + continue + if is_dir: + entries.append(_DirEntry(name=de.name, kind="dir")) + elif is_file: + try: + size = de.stat(follow_symlinks=False).st_size + except OSError: + size = None + entries.append(_DirEntry(name=de.name, kind="file", size=size)) + except PermissionError: + return _err(403, "permission denied") + entries.sort(key=lambda e: (e.kind == "file", e.name.lower())) + return _ResponseListDir(entries=entries).model_dump() + + # ----- /code/file_symbols ------------------------------------------------- + @app.route("/code/file_symbols", methods=["GET"]) + def code_file_symbols() -> tuple[dict[str, Any], int] | dict[str, Any]: + path = request.args.get("path", default=None, type=str) + if not path: + return _err(400, "path is required") + root = _get_project_root(dashboard_api) + if root is None: + return _err(503, "No active project", "no_project") + try: + resolved = resolve_project_path(root, path) + except ValueError as e: + return _err(400, str(e)) + except FileNotFoundError: + return _err(404, "file not found") + rel = os.path.relpath(resolved, str(Path(root).resolve())) + ls = _get_language_server_for_path(dashboard_api, rel) + if ls is None: + return _err(503, "Language server not ready", "ls_not_ready") + try: + doc_syms = ls.request_document_symbols(rel) + except TimeoutError as e: + return _err(504, str(e), "ls_timeout") + except Exception as e: # noqa: BLE001 + log.error("LSP document symbols failed for %s: %s", rel, e, exc_info=e) + return _err(502, str(e), "ls_error") + # doc_syms may be a DocumentSymbols object (.root_symbols) or a raw list. + roots = getattr(doc_syms, "root_symbols", None) + if roots is None: + roots = doc_syms or [] + return _ResponseFileSymbols(symbols=[_convert_lsp_symbol(s) for s in roots]).model_dump() + + # ----- /code/workspace_symbol_search -------------------------------------- + @app.route("/code/workspace_symbol_search", methods=["GET"]) + def code_workspace_symbol_search() -> tuple[dict[str, Any], int] | dict[str, Any]: + q = request.args.get("q", default="", type=str) + limit = request.args.get("limit", default=50, type=int) + q_stripped = q.strip() if q else "" + if len(q_stripped) < 2: + return _ResponseWorkspaceSymbolSearch(matches=[]).model_dump() + limit = max(1, min(limit, _WORKSPACE_SYMBOL_LIMIT_CAP)) + ls = _get_first_language_server(dashboard_api) + if ls is None: + return _err(503, "Language server not ready", "ls_not_ready") + try: + raw = ls.request_workspace_symbol(q) # singular — confirmed in solidlsp/ls.py + except TimeoutError as e: + return _err(504, str(e), "ls_timeout") + except Exception as e: # noqa: BLE001 + log.error("LSP workspace symbol failed for %r: %s", q, e, exc_info=e) + return _err(502, str(e), "ls_error") + if not raw: + return _ResponseWorkspaceSymbolSearch(matches=[]).model_dump() + raw = raw[:limit] + root = _get_project_root(dashboard_api) or "" + project_root_real = Path(root).resolve() if root else Path() + matches = [_convert_workspace_match(m, project_root_real) for m in raw] + return _ResponseWorkspaceSymbolSearch(matches=matches).model_dump() + + # ----- /code/diagnostics_summary ------------------------------------------ + @app.route("/code/diagnostics_summary", methods=["GET"]) + def code_diagnostics_summary() -> tuple[dict[str, Any], int] | dict[str, Any]: + file_limit = request.args.get("file_limit", default=1000, type=int) + file_limit = max(1, min(file_limit, _FILE_LIMIT_CAP)) + root = _get_project_root(dashboard_api) + if root is None: + return _err(503, "No active project", "no_project") + ls = _get_first_language_server(dashboard_api) + if ls is None: + return _err(503, "Language server not ready", "ls_not_ready") + + from serena.util.file_system import GitignoreParser + + try: + ignore: GitignoreParser | None = GitignoreParser(root) + except Exception: + ignore = None + + candidate_paths: list[str] = [] + root_real = str(Path(root).resolve()) + truncated = False + + for dirpath, dirnames, filenames in os.walk(root_real, followlinks=False): + # Skip dotfile dirs in-place. + dirnames[:] = [d for d in dirnames if not d.startswith(".")] + if ignore is not None: + kept_dirs: list[str] = [] + for d in dirnames: + rel_d = os.path.relpath(os.path.join(dirpath, d), root_real) + if not ignore.should_ignore(rel_d): + kept_dirs.append(d) + dirnames[:] = kept_dirs + for fn in filenames: + if fn.startswith("."): + continue + rel = os.path.relpath(os.path.join(dirpath, fn), root_real) + if ignore is not None and ignore.should_ignore(rel): + continue + if len(candidate_paths) >= file_limit: + truncated = True + break + candidate_paths.append(rel) + if truncated: + # Still need to skip remaining filenames but break outer next. + # Cleanest: just stop walking entirely once we've hit the cap. + dirnames[:] = [] + continue + + files: list[_FileDiagnostics] = [] + deadline = time.monotonic() + _DIAGNOSTICS_WALL_CLOCK_BUDGET_S + for rel in candidate_paths: + if time.monotonic() >= deadline: + # Overall wall-clock budget exceeded — return what we have so far. + truncated = True + break + try: + raw = ls.request_published_text_document_diagnostics(rel) + except TimeoutError: + continue # per-file timeout: skip + except Exception as e: # noqa: BLE001 + log.debug("Diagnostics failed for %s: %s", rel, e) + continue + if not raw: + continue + diags: list[_Diagnostic] = [] + # Running per-diagnostic byte estimate. Each serialized diag in the JSON + # list is roughly: field-name overhead (~80 bytes) + message + source. + # We track an estimate so we can stop before exceeding the 1MB cap + # without re-serializing the whole list on every trim. + byte_estimate = 0 + _PER_DIAG_JSON_OVERHEAD = 80 + for d in raw: + rng = _maybe(d, "range") or {} + rng_start = _maybe(rng, "start") or {} + try: + line = int(_maybe(rng_start, "line") or 0) + col = int(_maybe(rng_start, "character") or 0) + except (TypeError, ValueError): + line, col = 0, 0 + sev_int = _maybe(d, "severity") + try: + sev = _LSP_SEVERITY.get(int(sev_int) if sev_int is not None else 3, "info") + except (TypeError, ValueError): + sev = "info" + msg = _maybe(d, "message") or "" + if not isinstance(msg, str): + msg = str(msg) + if len(msg) > _DIAGNOSTICS_PER_MESSAGE_CAP: + msg = msg[:_DIAGNOSTICS_PER_MESSAGE_CAP] + truncated = True + source = _maybe(d, "source") + if source is not None and not isinstance(source, str): + source = str(source) + diag_size = len(msg.encode("utf-8")) + (len(source.encode("utf-8")) if source else 0) + _PER_DIAG_JSON_OVERHEAD + if byte_estimate + diag_size > _DIAGNOSTICS_PER_FILE_BYTE_CAP: + truncated = True + break + diags.append(_Diagnostic(severity=sev, message=msg, line=line, column=col, source=source)) + byte_estimate += diag_size + if not diags: + continue + files.append(_FileDiagnostics(path=rel, diagnostics=diags)) + + return _ResponseDiagnosticsSummary(files=files, truncated=truncated).model_dump() diff --git a/src/serena/resources/dashboard/assets/index-BBcJgPxG.css b/src/serena/resources/dashboard/assets/index-BBcJgPxG.css deleted file mode 100644 index 41aebc122..000000000 --- a/src/serena/resources/dashboard/assets/index-BBcJgPxG.css +++ /dev/null @@ -1 +0,0 @@ -:root{--bg: #f5f5f5;--bg-card: #ffffff;--bg-elevated: #ffffff;--bg-secondary-btn: #f0f2f5;--text-primary: #1f2328;--text-secondary: #3f4754;--text-muted: #6a737d;--border: #e3e6ea;--border-strong: #d0d7de;--accent: #eaa45d;--accent-hover: #dca662;--chart-2: #6aa3d8;--chart-3: #7fb77e;--chart-4: #d88c8c;--chart-5: #b39ddb;--chart-6: #e0a458;--chart-grid: #dddddd;--text-on-accent: #ffffff;--btn-disabled: #adb5bd;--tool-highlight: #fff3bf;--tool-highlight-text: #1f2328;--btn-secondary-hover: #e3e6ea;--stats-header: #f0f2f5;--success: #22c55e;--log-debug: #8b95a1;--log-info: #1f2328;--log-warning: #d97706;--log-error: #dc2626;--radius: 6px;--radius-sm: 4px;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-6: 24px;--space-8: 32px;--max-width: 1600px;--font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-mono: "JetBrains Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;--shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 1px 3px rgba(15, 23, 42, .06);--shadow-elevated: 0 4px 12px rgba(15, 23, 42, .08);--focus-ring: 0 0 0 3px rgba(234, 164, 93, .18)}[data-theme=dark]{--bg: #1a1a1a;--bg-card: #2d2d2d;--bg-elevated: #262b32;--bg-secondary-btn: #2d333b;--text-primary: #e6edf3;--text-secondary: #c9d1d9;--text-muted: #8b95a1;--border: #2d333b;--border-strong: #3d444d;--chart-grid: #444444;--btn-disabled: #4a5159;--tool-highlight: #f6c948;--tool-highlight-text: #15181c;--btn-secondary-hover: #3d444d;--stats-header: #262b32;--log-info: #e6edf3;--log-warning: #f59e0b;--log-error: #f87171;--shadow: 0 1px 2px rgba(0, 0, 0, .25), 0 1px 3px rgba(0, 0, 0, .35);--shadow-elevated: 0 4px 12px rgba(0, 0, 0, .45)}*{box-sizing:border-box}html{scrollbar-gutter:stable}body{margin:0;font-family:var(--font-sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background:var(--bg);color:var(--text-primary);transition:background-color .3s ease,color .3s ease}h1,h2,h3,h4,h5,h6{font-weight:600;letter-spacing:-.005em}code,pre,.mono{font-family:var(--font-mono)}a{color:inherit;text-decoration:none}input:focus-visible,textarea:focus-visible{outline:none;border-color:var(--accent);box-shadow:var(--focus-ring)}.modal-actions{display:flex;gap:var(--space-3);justify-content:flex-end;margin-top:var(--space-4)}.modal-info{color:var(--text-secondary)}.modal-hint{color:var(--text-muted);font-size:13px}.modal-input{width:100%;padding:var(--space-2);border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-card);color:var(--text-primary)}.modal-textarea{width:100%;font-family:var(--font-mono);font-size:13px;background:var(--bg-card);color:var(--text-primary);border:1px solid var(--border-strong);border-radius:var(--radius-sm);padding:var(--space-3)}.theme-toggle.svelte-ev54os{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer;color:var(--text-primary);padding:0;line-height:1}.theme-toggle.svelte-ev54os:hover{background:var(--bg-card)}.icon.svelte-ev54os{font-size:18px;line-height:1}.banner.svelte-1lo7lbo{position:relative;display:inline-flex;align-items:center}.banner.gold.svelte-1lo7lbo{display:flex;width:100%}.banner.gold.svelte-1lo7lbo a:where(.svelte-1lo7lbo){display:block;width:100%}.banner.svelte-1lo7lbo img:where(.svelte-1lo7lbo){max-height:90px;display:block}.banner.platinum.svelte-1lo7lbo img:where(.svelte-1lo7lbo){max-height:150px}.banner.gold.svelte-1lo7lbo img:where(.svelte-1lo7lbo){width:100%;height:auto;max-height:none;display:block}.header.svelte-xjepwq{display:flex;justify-content:space-between;align-items:center;gap:var(--space-6);padding:var(--space-6);background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);min-height:150px;max-width:var(--max-width);margin:0 auto}.header-left.svelte-xjepwq{display:flex;align-items:center;gap:var(--space-6)}.header-banner.svelte-xjepwq{display:flex;align-items:center}#serena-logo.svelte-xjepwq{height:130px}.header-nav.svelte-xjepwq{display:flex;flex-direction:column;align-items:flex-end;gap:var(--space-4)}.header-actions.svelte-xjepwq{position:relative;display:flex;gap:var(--space-2)}.icon-button.svelte-xjepwq{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer;color:var(--text-primary);padding:0;line-height:1}.icon-button.svelte-xjepwq:hover{background:var(--bg-card)}.icon-button.svelte-xjepwq .icon:where(.svelte-xjepwq){font-size:18px;line-height:1}.shutdown-button.svelte-xjepwq:hover{color:var(--danger, #c0392b);border-color:var(--danger, #c0392b)}.header-tabs.svelte-xjepwq{display:flex;gap:var(--space-5)}.header-tab.svelte-xjepwq{background:none;border:none;cursor:pointer;color:var(--text-primary);font-family:var(--font-sans);font-size:1.05rem;font-weight:600;letter-spacing:.01em;padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);border-bottom:3px solid transparent;transition:background-color .12s ease,color .12s ease,border-color .12s ease}.header-tab.svelte-xjepwq:hover{background:var(--bg-secondary-btn)}.header-tab.active.svelte-xjepwq{color:var(--accent);border-bottom-color:var(--accent)}.collapsible-header.svelte-oy8gea{margin:0;font-size:inherit;font-weight:inherit}.collapsible-trigger.svelte-oy8gea{display:flex;justify-content:space-between;align-items:center;cursor:pointer;width:100%;background:none;border:none;padding:0;text-align:left}.collapsible-title.svelte-oy8gea{text-transform:uppercase;letter-spacing:.04em;font-size:15px;font-weight:600;color:var(--text-muted)}.collapsible-trigger.svelte-oy8gea:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.toggle-icon.svelte-oy8gea{font-size:10px;color:var(--text-muted);transition:transform .2s}.toggle-icon.open.svelte-oy8gea{transform:rotate(180deg)}.collapsible-content.svelte-oy8gea{margin-top:var(--space-3)}.btn.svelte-18f749u{font-family:var(--font-sans);font-weight:500;border:none;border-radius:var(--radius-sm);padding:var(--space-2) var(--space-4);cursor:pointer}.primary.svelte-18f749u{background:var(--accent);color:var(--text-on-accent)}.primary.svelte-18f749u:hover{background:var(--accent-hover)}.secondary.svelte-18f749u{background:var(--bg-secondary-btn);color:var(--text-primary)}.secondary.svelte-18f749u:hover{background:var(--btn-secondary-hover)}.danger.svelte-18f749u{background:var(--log-error);color:var(--text-on-accent)}.btn.svelte-18f749u:disabled{background:var(--btn-disabled);cursor:not-allowed}.config-grid.svelte-3p7ghk{display:grid;grid-template-columns:160px 1fr;gap:var(--space-2) var(--space-3);align-items:baseline;margin-bottom:var(--space-4)}.config-label.svelte-3p7ghk{text-transform:uppercase;letter-spacing:.03em;font-size:13px;font-weight:500;color:var(--text-muted)}.config-value.svelte-3p7ghk{color:var(--text-primary)}.config-display.svelte-3p7ghk .collapsible{margin-top:var(--space-4)}.languages-cell.svelte-3p7ghk{display:inline-flex;flex-wrap:wrap;gap:var(--space-2);align-items:center}.lang-badge.svelte-3p7ghk{background:var(--bg-secondary-btn);border-radius:var(--radius-sm);padding:2px var(--space-2);font-family:var(--font-mono);font-size:12px;display:inline-flex;align-items:center;gap:4px}.lang-remove.svelte-3p7ghk{border:none;background:none;cursor:pointer;color:var(--log-error);font-weight:700;line-height:1;padding:0 2px;border-radius:3px}.lang-remove.svelte-3p7ghk:hover{background:var(--bg-secondary-btn)}.tools-grid.svelte-3p7ghk{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:var(--space-2)}.tool-chip.svelte-3p7ghk{background:var(--bg);padding:6px 10px;border-radius:3px;font-family:var(--font-mono);font-size:13px;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.memories-container.svelte-3p7ghk{display:flex;flex-wrap:wrap;gap:var(--space-2)}.memory-item.svelte-3p7ghk{position:relative;display:inline-flex;align-items:center;padding:8px 30px 8px 12px;border-radius:4px;background:var(--bg);border:1px solid var(--border)}.memory-name.svelte-3p7ghk{background:none;border:none;cursor:pointer;font-family:var(--font-mono);font-size:13px;color:var(--text-primary);padding:0}.memory-remove.svelte-3p7ghk{position:absolute;top:2px;right:2px;width:18px;height:18px;display:flex;align-items:center;justify-content:center;background:none;border:none;border-radius:3px;cursor:pointer;color:var(--log-error);font-size:14px;font-weight:700;line-height:1}.memory-remove.svelte-3p7ghk:hover{background:var(--bg-secondary-btn)}.memory-add-btn.svelte-3p7ghk{display:inline-flex;align-items:center;padding:8px 12px;border-radius:4px;border:1px dashed var(--border-strong);background:var(--bg-card);color:var(--text-primary);cursor:pointer;font-family:var(--font-sans);font-size:13px}.memory-add-btn.svelte-3p7ghk:hover{border-color:var(--accent)}.config-footer.svelte-3p7ghk{display:flex;justify-content:space-between;align-items:center;gap:var(--space-3);margin-top:var(--space-4);flex-wrap:wrap}.config-guide-link.svelte-3p7ghk{color:var(--accent);font-weight:500;text-decoration:none}.config-guide-link.svelte-3p7ghk:hover{text-decoration:underline}.bar-row.svelte-vf5ryt{display:grid;grid-template-columns:1fr 3fr auto;gap:var(--space-2);align-items:center;margin:var(--space-1) 0}.bar-name.svelte-vf5ryt{font-family:var(--font-mono);font-size:12px}.bar-track.svelte-vf5ryt{background:var(--bg-secondary-btn);border-radius:var(--radius-sm);height:14px}.bar-fill.svelte-vf5ryt{background:var(--accent);height:100%;border-radius:var(--radius-sm)}.bar-count.svelte-vf5ryt{font-size:12px;color:var(--text-muted)}.no-stats-message.svelte-vf5ryt{color:var(--text-muted)}.card.svelte-xtxwfc{background:var(--bg-card);border:1px solid var(--border);padding:var(--space-6);border-radius:var(--radius);box-shadow:var(--shadow);margin-bottom:var(--space-4)}.card-title.svelte-xtxwfc{text-transform:uppercase;letter-spacing:.04em;font-size:15px;font-weight:600;color:var(--text-muted);margin:0 0 var(--space-4) 0}.list-panel.svelte-1mlfgti{list-style:none;margin:0;padding:0;max-height:340px;overflow-y:auto;display:flex;flex-direction:column;gap:var(--space-1)}.list-panel.svelte-1mlfgti li:where(.svelte-1mlfgti){padding:var(--space-2);border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-elevated);font-family:var(--font-mono);font-size:12px;color:var(--text-primary)}.list-panel.svelte-1mlfgti li.active:where(.svelte-1mlfgti){background:var(--accent);color:var(--text-on-accent);border-color:var(--accent)}.no-stats-message.svelte-1mlfgti{color:var(--text-muted)}.projects.svelte-19jixoo{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--space-2)}.project.svelte-19jixoo{padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);background:var(--bg);border:1px solid var(--border)}.project.active.svelte-19jixoo{background:var(--accent);color:var(--text-on-accent);border-color:var(--accent)}.project-name.svelte-19jixoo{font-weight:700;font-size:13px;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.project-path.svelte-19jixoo{font-family:var(--font-mono);font-size:11px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.project.active.svelte-19jixoo .project-path:where(.svelte-19jixoo){color:var(--text-on-accent);opacity:.85}.no-stats-message.svelte-19jixoo{color:var(--text-muted)}.spinner.svelte-1wjj49t{width:16px;height:16px;border:2px solid var(--border-strong);border-top-color:var(--accent);border-radius:50%;animation:svelte-1wjj49t-spin .7s linear infinite;display:inline-block}@keyframes svelte-1wjj49t-spin{to{transform:rotate(360deg)}}.execution-item.svelte-1wrcimm{display:inline-flex;align-items:center;gap:var(--space-2);border:1px solid var(--border);border-radius:999px;padding:var(--space-1) var(--space-3);margin:2px}.execution-item.running.svelte-1wrcimm{border-color:var(--accent)}.execution-name.svelte-1wrcimm{font-family:var(--font-mono);font-size:12px}.cancel-btn.svelte-1wrcimm{border:none;background:none;cursor:pointer;color:var(--log-error);font-weight:700;line-height:1;padding:0 2px;border-radius:3px}.cancel-btn.svelte-1wrcimm:hover{background:var(--bg-secondary-btn)}.cancel-error.svelte-1wrcimm{color:var(--log-error);margin:var(--space-2) 0 0}.no-stats-message.svelte-1wrcimm{color:var(--text-muted)}.last-exec.svelte-1772y66{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);border:1px solid var(--border);border-radius:var(--radius)}.last-exec.ok.svelte-1772y66{border-color:var(--success);background:color-mix(in srgb,var(--success) 8%,transparent)}.last-exec.fail.svelte-1772y66{border-color:var(--log-error);background:color-mix(in srgb,var(--log-error) 8%,transparent)}.icon.svelte-1772y66{display:inline-flex;width:22px;height:22px;align-items:center;justify-content:center;border-radius:50%;font-size:12px}.ok.svelte-1772y66 .icon:where(.svelte-1772y66){background:color-mix(in srgb,var(--success) 20%,transparent);color:var(--success)}.fail.svelte-1772y66 .icon:where(.svelte-1772y66){background:color-mix(in srgb,var(--log-error) 20%,transparent);color:var(--log-error)}.body.svelte-1772y66{display:flex;flex-direction:column}.status.svelte-1772y66{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted)}.execution-name.svelte-1772y66{font-family:var(--font-mono);font-size:13px;color:var(--text-primary)}.meta.svelte-1772y66{margin-left:auto;font-family:var(--font-mono);font-size:12px;color:var(--text-muted)}.no-stats-message.svelte-1772y66{color:var(--text-muted)}.cancelled-item.svelte-1re8sys{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-1) 0;font-family:var(--font-mono);font-size:12px;color:var(--text-secondary)}.icon.svelte-1re8sys{display:inline-flex;width:16px;height:16px;align-items:center;justify-content:center;border-radius:50%;background:var(--bg-secondary-btn);color:var(--log-error);font-size:10px}.cancelled-item.abandoned.svelte-1re8sys .icon:where(.svelte-1re8sys){color:var(--log-warning)}.meta.svelte-1re8sys{margin-left:auto;color:var(--text-muted)}.news-item.svelte-1yvzfte{border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-3);margin-bottom:var(--space-3);background:var(--bg-card);box-shadow:var(--shadow)}.news-dismiss.svelte-1yvzfte{margin-top:var(--space-2);background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);padding:var(--space-1) var(--space-3);cursor:pointer;color:var(--text-primary)}.overview-container.svelte-1im2p0o{display:grid;grid-template-columns:minmax(0,1fr) 400px;gap:var(--space-6)}@media(max-width:1000px){.overview-container.svelte-1im2p0o{grid-template-columns:1fr}}.log-action-buttons.svelte-pdgm8l{display:flex;gap:var(--space-2);justify-content:flex-end;margin-bottom:var(--space-2)}.log-action-btn.svelte-pdgm8l{background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);padding:var(--space-1) var(--space-3);cursor:pointer;color:var(--text-primary)}.log-action-btn.svelte-pdgm8l:disabled{color:var(--btn-disabled);cursor:not-allowed}.danger.svelte-pdgm8l:not(:disabled){color:var(--log-error)}.log-container.svelte-1f7p4v8{height:calc(100vh - 220px);overflow:auto;font-family:var(--font-mono);font-size:12px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-3);box-shadow:var(--shadow)}.log-line.svelte-1f7p4v8{white-space:pre-wrap}.debug.svelte-1f7p4v8{color:var(--log-debug)}.info.svelte-1f7p4v8{color:var(--log-info)}.warning.svelte-1f7p4v8{color:var(--log-warning)}.error.svelte-1f7p4v8{color:var(--log-error)}.log-line .tool-name{background-color:var(--tool-highlight);color:var(--tool-highlight-text);font-weight:700}.chart-group.svelte-1tmlu4m{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-4);box-shadow:var(--shadow)}.canvas-wrap.svelte-1tmlu4m{position:relative}.kpi-strip.svelte-ebpvur{display:flex;flex-wrap:wrap;gap:var(--space-3);margin-bottom:var(--space-3)}.kpi.svelte-ebpvur{flex:1 1 0;min-width:140px;display:flex;flex-direction:column;gap:var(--space-1);padding:var(--space-3) var(--space-4);background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow)}.kpi-total.svelte-ebpvur{border-color:var(--accent)}.kpi-label.svelte-ebpvur{font-size:.75rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--text-muted)}.kpi-value.svelte-ebpvur{font-family:var(--font-mono);font-size:1.4rem;font-weight:700;color:var(--text-primary);line-height:1.1}.kpi-total.svelte-ebpvur .kpi-value:where(.svelte-ebpvur){color:var(--accent)}.controls.svelte-af288w{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3);margin-bottom:var(--space-4);background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow)}.charts-grid.svelte-af288w{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-4);margin-top:var(--space-4)}.charts-bars.svelte-af288w{display:grid;grid-template-columns:1fr;gap:var(--space-4);margin-top:var(--space-4)}@media(max-width:1000px){.charts-grid.svelte-af288w{grid-template-columns:1fr}}.estimator-name.svelte-af288w{color:var(--text-muted);margin:var(--space-2) 0}.no-stats-message.svelte-af288w{color:var(--text-muted)}.backdrop.svelte-19jhfg1{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000073;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:1000}.modal-content.svelte-19jhfg1{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-6);max-width:600px;width:90%;box-shadow:var(--shadow-elevated);position:relative}.modal-error.svelte-19jhfg1{color:var(--log-error);margin:0 0 var(--space-3)}.modal-close.svelte-19jhfg1{position:absolute;top:var(--space-3);right:var(--space-4);cursor:pointer;font-size:22px;color:var(--text-muted);background:none;border:none;line-height:1;padding:0}.combobox.svelte-hol9vh{position:relative}input.svelte-hol9vh{width:100%;padding:var(--space-2);border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-card);color:var(--text-primary);font-family:var(--font-sans)}.combobox-caret.svelte-hol9vh{position:absolute;right:var(--space-2);top:var(--space-2);pointer-events:none}.combobox-options.svelte-hol9vh{list-style:none;margin:0;padding:0;position:absolute;z-index:5;width:100%;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-sm);max-height:200px;overflow:auto}.combobox-options.svelte-hol9vh li:where(.svelte-hol9vh){padding:var(--space-2);cursor:pointer}.combobox-options.svelte-hol9vh li:where(.svelte-hol9vh):hover{background:var(--bg-secondary-btn)}.combobox-empty.svelte-hol9vh{padding:var(--space-2);color:var(--text-muted)}.memory-rename-input.svelte-blcc47{font-family:var(--font-mono);font-size:14px;padding:var(--space-1) var(--space-2);border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-card);color:var(--text-primary)}.memory-name-display.svelte-blcc47{font-family:var(--font-mono)}.rename-trigger.svelte-blcc47{background:none;border:none;cursor:pointer;color:var(--text-muted)}.main.svelte-1n46o8q{max-width:var(--max-width);margin:0 auto;padding:var(--space-6)} diff --git a/src/serena/resources/dashboard/assets/index-ByT_227W.js b/src/serena/resources/dashboard/assets/index-ByT_227W.js deleted file mode 100644 index 0b10e1892..000000000 --- a/src/serena/resources/dashboard/assets/index-ByT_227W.js +++ /dev/null @@ -1,30 +0,0 @@ -var lu=Object.defineProperty;var va=e=>{throw TypeError(e)};var cu=(e,t,n)=>t in e?lu(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var D=(e,t,n)=>cu(e,typeof t!="symbol"?t+"":t,n),jo=(e,t,n)=>t.has(e)||va("Cannot "+n);var k=(e,t,n)=>(jo(e,t,"read from private field"),n?n.call(e):t.get(e)),X=(e,t,n)=>t.has(e)?va("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n),Y=(e,t,n,i)=>(jo(e,t,"write to private field"),i?i.call(e,n):t.set(e,n),n),nt=(e,t,n)=>(jo(e,t,"access private method"),n);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))i(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const r of o.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&i(r)}).observe(document,{childList:!0,subtree:!0});function n(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function i(s){if(s.ep)return;s.ep=!0;const o=n(s);fetch(s.href,o)}})();const nc=!1;var ic=Array.isArray,hu=Array.prototype.indexOf,Bn=Array.prototype.includes,Co=Array.from,uu=Object.defineProperty,Vi=Object.getOwnPropertyDescriptor,fu=Object.getOwnPropertyDescriptors,du=Object.prototype,gu=Array.prototype,sc=Object.getPrototypeOf,_a=Object.isExtensible;const pu=()=>{};function mu(e){for(var t=0;t{e=i,t=s});return{promise:n,resolve:e,reject:t}}function rc(e,t){if(Array.isArray(e))return e;if(!(Symbol.iterator in e))return Array.from(e);const n=[];for(const i of e)if(n.push(i),n.length===t)break;return n}const Ot=2,pi=4,Ao=8,ac=1<<24,ve=16,ke=32,vn=64,hr=128,ce=512,St=1024,Ct=2048,Re=4096,Bt=8192,xe=16384,Qn=32768,ur=1<<25,qn=65536,co=1<<17,vu=1<<18,Mi=1<<19,_u=1<<20,Ee=1<<25,$n=65536,ho=1<<21,ai=1<<22,gn=1<<23,qs=Symbol("$state"),bu=Symbol(""),$s=Symbol("attributes"),fr=Symbol("class"),dr=Symbol("style"),Ri=Symbol("text"),Ks=Symbol("form reset"),Do=new class extends Error{constructor(){super(...arguments);D(this,"name","StaleReactionError");D(this,"message","The reaction that called `getAbortSignal()` was re-run or destroyed")}};function lc(e){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function xu(){throw new Error("https://svelte.dev/e/async_derived_orphan")}function yu(e,t,n){throw new Error("https://svelte.dev/e/each_key_duplicate")}function wu(e){throw new Error("https://svelte.dev/e/effect_in_teardown")}function ku(){throw new Error("https://svelte.dev/e/effect_in_unowned_derived")}function Mu(e){throw new Error("https://svelte.dev/e/effect_orphan")}function Su(){throw new Error("https://svelte.dev/e/effect_update_depth_exceeded")}function Pu(){throw new Error("https://svelte.dev/e/state_descriptors_fixed")}function Cu(){throw new Error("https://svelte.dev/e/state_prototype_fixed")}function Au(){throw new Error("https://svelte.dev/e/state_unsafe_mutation")}function Du(){throw new Error("https://svelte.dev/e/svelte_boundary_reset_onerror")}const Tu=1,Ou=2,cc=4,Eu=8,Lu=16,Ru=1,Iu=2,Mt=Symbol("uninitialized"),hc="http://www.w3.org/1999/xhtml",Fu="http://www.w3.org/2000/svg",zu="http://www.w3.org/1998/Math/MathML";function Nu(){console.warn("https://svelte.dev/e/derived_inert")}function Bu(){console.warn("https://svelte.dev/e/svelte_boundary_reset_noop")}function uc(e){return e===this.v}function ju(e,t){return e!=e?t==t:e!==t||e!==null&&typeof e=="object"||typeof e=="function"}function fc(e){return!ju(e,this.v)}let ps=!1,Vu=!1;function Wu(){ps=!0}let _t=null;function mi(e){_t=e}function at(e,t=!1,n){_t={p:_t,i:!1,c:null,e:null,s:e,x:null,r:Q,l:ps&&!t?{s:null,u:null,$:[]}:null}}function lt(e){var t=_t,n=t.e;if(n!==null){t.e=null;for(var i of n)Rc(i)}return t.i=!0,_t=t.p,{}}function Si(){return!ps||_t!==null&&_t.l===null}let Dn=[];function dc(){var e=Dn;Dn=[],mu(e)}function pn(e){if(Dn.length===0&&!Hi){var t=Dn;queueMicrotask(()=>{t===Dn&&dc()})}Dn.push(e)}function Hu(){for(;Dn.length>0;)dc()}function gc(e){var t=Q;if(t===null)return J.f|=gn,e;if((t.f&Qn)===0&&(t.f&pi)===0)throw e;an(e,t)}function an(e,t){for(;t!==null;){if((t.f&hr)!==0){if((t.f&Qn)===0)throw e;try{t.b.error(e);return}catch(n){e=n}}t=t.parent}throw e}const Yu=-7169;function mt(e,t){e.f=e.f&Yu|t}function Hr(e){(e.f&ce)!==0||e.deps===null?mt(e,St):mt(e,Re)}function pc(e){if(e!==null)for(const t of e)(t.f&Ot)===0||(t.f&$n)===0||(t.f^=$n,pc(t.deps))}function mc(e,t,n){(e.f&Ct)!==0?t.add(e):(e.f&Re)!==0&&n.add(e),pc(e.deps),mt(e,St)}let Vo=null,ei=null,N=null,Wi=null,Dt=null,gr=null,Hi=!1,Wo=!1,oi=null,Gs=null;var ba=0;let Uu=1;var ci,sn,Ln,hi,ui,Rn,fi,We,cs,Jt,hs,on,Ae,De,di,In,ot,pr,Ii,mr,vc,_c,Js,Xu,vr,si;const Mo=class Mo{constructor(){X(this,ot);D(this,"id",Uu++);X(this,ci,!1);D(this,"linked",!0);X(this,sn,null);X(this,Ln,null);D(this,"async_deriveds",new Map);D(this,"current",new Map);D(this,"previous",new Map);D(this,"unblocked",new Set);X(this,hi,new Set);X(this,ui,new Set);X(this,Rn,new Set);X(this,fi,0);X(this,We,new Map);X(this,cs,null);X(this,Jt,[]);X(this,hs,[]);X(this,on,new Set);X(this,Ae,new Set);X(this,De,new Map);X(this,di,new Set);D(this,"is_fork",!1);X(this,In,!1)}skip_effect(t){k(this,De).has(t)||k(this,De).set(t,{d:[],m:[]}),k(this,di).delete(t)}unskip_effect(t,n=i=>this.schedule(i)){var i=k(this,De).get(t);if(i){k(this,De).delete(t);for(var s of i.d)mt(s,Ct),n(s);for(s of i.m)mt(s,Re),n(s)}k(this,di).add(t)}capture(t,n,i=!1){t.v!==Mt&&!this.previous.has(t)&&this.previous.set(t,t.v),(t.f&gn)===0&&(this.current.set(t,[n,i]),Dt==null||Dt.set(t,n)),this.is_fork||(t.v=n)}activate(){N=this}deactivate(){N=null,Dt=null}flush(){try{Wo=!0,N=this,nt(this,ot,Ii).call(this)}finally{ba=0,gr=null,oi=null,Gs=null,Wo=!1,N=null,Dt=null,jn.clear()}}discard(){for(const t of k(this,ui))t(this);k(this,ui).clear(),k(this,Rn).clear(),nt(this,ot,si).call(this)}register_created_effect(t){k(this,hs).push(t)}increment(t,n){if(Y(this,fi,k(this,fi)+1),t){let i=k(this,We).get(n)??0;k(this,We).set(n,i+1)}}decrement(t,n){if(Y(this,fi,k(this,fi)-1),t){let i=k(this,We).get(n)??0;i===1?k(this,We).delete(n):k(this,We).set(n,i-1)}k(this,In)||(Y(this,In,!0),pn(()=>{Y(this,In,!1),this.linked&&this.flush()}))}transfer_effects(t,n){for(const i of t)k(this,on).add(i);for(const i of n)k(this,Ae).add(i);t.clear(),n.clear()}oncommit(t){k(this,hi).add(t)}ondiscard(t){k(this,ui).add(t)}on_fork_commit(t){k(this,Rn).add(t)}run_fork_commit_callbacks(){for(const t of k(this,Rn))t(this);k(this,Rn).clear()}settled(){return(k(this,cs)??Y(this,cs,oc())).promise}static ensure(){var t;if(N===null){const n=N=new Mo;nt(t=n,ot,vr).call(t),!Wo&&!Hi&&pn(()=>{k(n,ci)||n.flush()})}return N}apply(){{Dt=null;return}}schedule(t){var s;if(gr=t,(s=t.b)!=null&&s.is_pending&&(t.f&(pi|Ao|ac))!==0&&(t.f&Qn)===0){t.b.defer_effect(t);return}for(var n=t;n.parent!==null;){n=n.parent;var i=n.f;if(oi!==null&&n===Q&&(J===null||(J.f&Ot)===0))return;if((i&(vn|ke))!==0){if((i&St)===0)return;n.f^=St}}k(this,Jt).push(n)}};ci=new WeakMap,sn=new WeakMap,Ln=new WeakMap,hi=new WeakMap,ui=new WeakMap,Rn=new WeakMap,fi=new WeakMap,We=new WeakMap,cs=new WeakMap,Jt=new WeakMap,hs=new WeakMap,on=new WeakMap,Ae=new WeakMap,De=new WeakMap,di=new WeakMap,In=new WeakMap,ot=new WeakSet,pr=function(){if(this.is_fork)return!0;for(const i of k(this,We).keys()){for(var t=i,n=!1;t.parent!==null;){if(k(this,De).has(t)){n=!0;break}t=t.parent}if(!n)return!0}return!1},Ii=function(){var l,c,h,u;if(Y(this,ci,!0),ba++>1e3&&(nt(this,ot,si).call(this),$u()),!nt(this,ot,pr).call(this)){for(const f of k(this,on))k(this,Ae).delete(f),mt(f,Ct),this.schedule(f);for(const f of k(this,Ae))mt(f,Re),this.schedule(f)}const t=k(this,Jt);Y(this,Jt,[]),this.apply();var n=oi=[],i=[],s=Gs=[];for(const f of t)try{nt(this,ot,mr).call(this,f,n,i)}catch(d){throw yc(f),d}if(N=null,s.length>0){var o=Mo.ensure();for(const f of s)o.schedule(f)}if(oi=null,Gs=null,nt(this,ot,pr).call(this)){nt(this,ot,Js).call(this,i),nt(this,ot,Js).call(this,n);for(const[f,d]of k(this,De))xc(f,d);s.length>0&&nt(l=N,ot,Ii).call(l);return}const r=nt(this,ot,vc).call(this);if(r){nt(c=r,ot,_c).call(c,this);return}k(this,on).clear(),k(this,Ae).clear();for(const f of k(this,hi))f(this);k(this,hi).clear(),Wi=this,xa(i),xa(n),Wi=null,(h=k(this,cs))==null||h.resolve();var a=N;if(this.linked&&k(this,fi)===0&&nt(this,ot,si).call(this),k(this,Jt).length>0){a===null&&(a=this,nt(this,ot,vr).call(this));const f=a;k(f,Jt).push(...k(this,Jt).filter(d=>!k(f,Jt).includes(d)))}a!==null&&nt(u=a,ot,Ii).call(u)},mr=function(t,n,i){t.f^=St;for(var s=t.first;s!==null;){var o=s.f,r=(o&(ke|vn))!==0,a=r&&(o&St)!==0,l=a||(o&Bt)!==0||k(this,De).has(s);if(!l&&s.fn!==null){r?s.f^=St:(o&pi)!==0?n.push(s):_s(s)&&((o&ve)!==0&&k(this,Ae).add(s),_i(s));var c=s.first;if(c!==null){s=c;continue}}for(;s!==null;){var h=s.next;if(h!==null){s=h;break}s=s.parent}}},vc=function(){for(var t=k(this,sn);t!==null;){if(!t.is_fork){for(const[n,[,i]]of this.current)if(t.current.has(n)&&!i)return t}t=k(t,sn)}return null},_c=function(t){var i;for(const[s,o]of t.current)!this.previous.has(s)&&t.previous.has(s)&&this.previous.set(s,t.previous.get(s)),this.current.set(s,o);for(const[s,o]of t.async_deriveds){const r=this.async_deriveds.get(s);r&&o.promise.then(r.resolve)}const n=s=>{var o=s.reactions;if(o!==null)for(const l of o){var r=l.f;if((r&Ot)!==0)n(l);else{var a=l;r&(ai|ve)&&!this.async_deriveds.has(a)&&(k(this,Ae).delete(a),mt(a,Ct),this.schedule(a))}}};for(const s of this.current.keys())n(s);this.oncommit(()=>t.discard()),nt(i=t,ot,si).call(i),N=this,nt(this,ot,Ii).call(this)},Js=function(t){for(var n=0;n!this.current.has(f));if(s.length===0)t&&u.discard();else if(n.length>0){if(t)for(const f of k(this,di))u.unskip_effect(f,d=>{var g;(d.f&(ve|ai))!==0?u.schedule(d):nt(g=u,ot,Js).call(g,[d])});u.activate();var o=new Set,r=new Map;for(var a of n)bc(a,s,o,r);r=new Map;var l=[...u.current.keys()].filter(f=>this.current.has(f)?this.current.get(f)[0]!==f.v:!0);if(l.length>0)for(const f of k(this,hs))(f.f&(xe|Bt|co))===0&&Yr(f,l,r)&&((f.f&(ai|ve))!==0?(mt(f,Ct),u.schedule(f)):k(u,on).add(f));if(k(u,Jt).length>0&&!k(u,In)){u.apply();for(var c of k(u,Jt))nt(h=u,ot,mr).call(h,c,[],[]);Y(u,Jt,[])}u.deactivate()}}}},vr=function(){ei===null?Vo=ei=this:(Y(ei,Ln,this),Y(this,sn,ei)),ei=this},si=function(){var t=k(this,sn),n=k(this,Ln);t===null?Vo=n:Y(t,Ln,n),n===null?ei=t:Y(n,sn,t),this.linked=!1};let Kn=Mo;function qu(e){var t=Hi;Hi=!0;try{for(var n;;){if(Hu(),N===null)return n;N.flush()}}finally{Hi=t}}function $u(){try{Su()}catch(e){an(e,gr)}}let ge=null;function xa(e){var t=e.length;if(t!==0){for(var n=0;n0)){jn.clear();for(const s of ge){if((s.f&(xe|Bt))!==0)continue;const o=[s];let r=s.parent;for(;r!==null;)ge.has(r)&&(ge.delete(r),o.push(r)),r=r.parent;for(let a=o.length-1;a>=0;a--){const l=o[a];(l.f&(xe|Bt))===0&&_i(l)}}ge.clear()}}ge=null}}function bc(e,t,n,i){if(!n.has(e)&&(n.add(e),e.reactions!==null))for(const s of e.reactions){const o=s.f;(o&Ot)!==0?bc(s,t,n,i):(o&(ai|ve))!==0&&(o&Ct)===0&&Yr(s,t,i)&&(mt(s,Ct),Ur(s))}}function Yr(e,t,n){const i=n.get(e);if(i!==void 0)return i;if(e.deps!==null)for(const s of e.deps){if(Bn.call(t,s))return!0;if((s.f&Ot)!==0&&Yr(s,t,n))return n.set(s,!0),!0}return n.set(e,!1),!1}function Ur(e){N.schedule(e)}function xc(e,t){if(!((e.f&ke)!==0&&(e.f&St)!==0)){(e.f&Ct)!==0?t.d.push(e):(e.f&Re)!==0&&t.m.push(e),mt(e,St);for(var n=e.first;n!==null;)xc(n,t),n=n.next}}function yc(e){mt(e,St);for(var t=e.first;t!==null;)yc(t),t=t.next}function Ku(e){let t=0,n=Gn(0),i;return()=>{$r()&&(b(n),Kr(()=>(t===0&&(i=Ge(()=>e(()=>Yi(n)))),t+=1,()=>{pn(()=>{t-=1,t===0&&(i==null||i(),i=void 0,Yi(n))})})))}}var Gu=qn|Mi;function Ju(e,t,n,i){new Zu(e,t,n,i)}var se,Wr,oe,Fn,Wt,re,Nt,Zt,He,zn,rn,gi,us,fs,Ye,So,bt,Qu,tf,ef,_r,Zs,Qs,br,xr;class Zu{constructor(t,n,i,s){X(this,bt);D(this,"parent");D(this,"is_pending",!1);D(this,"transform_error");X(this,se);X(this,Wr,null);X(this,oe);X(this,Fn);X(this,Wt);X(this,re,null);X(this,Nt,null);X(this,Zt,null);X(this,He,null);X(this,zn,0);X(this,rn,0);X(this,gi,!1);X(this,us,new Set);X(this,fs,new Set);X(this,Ye,null);X(this,So,Ku(()=>(Y(this,Ye,Gn(k(this,zn))),()=>{Y(this,Ye,null)})));var o;Y(this,se,t),Y(this,oe,n),Y(this,Fn,r=>{var a=Q;a.b=this,a.f|=hr,i(r)}),this.parent=Q.b,this.transform_error=s??((o=this.parent)==null?void 0:o.transform_error)??(r=>r),Y(this,Wt,vs(()=>{nt(this,bt,_r).call(this)},Gu))}defer_effect(t){mc(t,k(this,us),k(this,fs))}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!k(this,oe).pending}update_pending_count(t,n){nt(this,bt,br).call(this,t,n),Y(this,zn,k(this,zn)+t),!(!k(this,Ye)||k(this,gi))&&(Y(this,gi,!0),pn(()=>{Y(this,gi,!1),k(this,Ye)&&vi(k(this,Ye),k(this,zn))}))}get_effect_pending(){return k(this,So).call(this),b(k(this,Ye))}error(t){if(!k(this,oe).onerror&&!k(this,oe).failed)throw t;N!=null&&N.is_fork?(k(this,re)&&N.skip_effect(k(this,re)),k(this,Nt)&&N.skip_effect(k(this,Nt)),k(this,Zt)&&N.skip_effect(k(this,Zt)),N.on_fork_commit(()=>{nt(this,bt,xr).call(this,t)})):nt(this,bt,xr).call(this,t)}}se=new WeakMap,Wr=new WeakMap,oe=new WeakMap,Fn=new WeakMap,Wt=new WeakMap,re=new WeakMap,Nt=new WeakMap,Zt=new WeakMap,He=new WeakMap,zn=new WeakMap,rn=new WeakMap,gi=new WeakMap,us=new WeakMap,fs=new WeakMap,Ye=new WeakMap,So=new WeakMap,bt=new WeakSet,Qu=function(){try{Y(this,re,ae(()=>k(this,Fn).call(this,k(this,se))))}catch(t){this.error(t)}},tf=function(t){const n=k(this,oe).failed;n&&Y(this,Zt,ae(()=>{n(k(this,se),()=>t,()=>()=>{})}))},ef=function(){const t=k(this,oe).pending;t&&(this.is_pending=!0,Y(this,Nt,ae(()=>t(k(this,se)))),pn(()=>{var n=Y(this,He,document.createDocumentFragment()),i=Ke();n.append(i),Y(this,re,nt(this,bt,Qs).call(this,()=>ae(()=>k(this,Fn).call(this,i)))),k(this,rn)===0&&(k(this,se).before(n),Y(this,He,null),Wn(k(this,Nt),()=>{Y(this,Nt,null)}),nt(this,bt,Zs).call(this,N))}))},_r=function(){try{if(this.is_pending=this.has_pending_snippet(),Y(this,rn,0),Y(this,zn,0),Y(this,re,ae(()=>{k(this,Fn).call(this,k(this,se))})),k(this,rn)>0){var t=Y(this,He,document.createDocumentFragment());Zr(k(this,re),t);const n=k(this,oe).pending;Y(this,Nt,ae(()=>n(k(this,se))))}else nt(this,bt,Zs).call(this,N)}catch(n){this.error(n)}},Zs=function(t){this.is_pending=!1,t.transfer_effects(k(this,us),k(this,fs))},Qs=function(t){var n=Q,i=J,s=_t;Ie(k(this,Wt)),fe(k(this,Wt)),mi(k(this,Wt).ctx);try{return Kn.ensure(),t()}catch(o){return gc(o),null}finally{Ie(n),fe(i),mi(s)}},br=function(t,n){var i;if(!this.has_pending_snippet()){this.parent&&nt(i=this.parent,bt,br).call(i,t,n);return}Y(this,rn,k(this,rn)+t),k(this,rn)===0&&(nt(this,bt,Zs).call(this,n),k(this,Nt)&&Wn(k(this,Nt),()=>{Y(this,Nt,null)}),k(this,He)&&(k(this,se).before(k(this,He)),Y(this,He,null)))},xr=function(t){k(this,re)&&(Ut(k(this,re)),Y(this,re,null)),k(this,Nt)&&(Ut(k(this,Nt)),Y(this,Nt,null)),k(this,Zt)&&(Ut(k(this,Zt)),Y(this,Zt,null));var n=k(this,oe).onerror;let i=k(this,oe).failed;var s=!1,o=!1;const r=()=>{if(s){Bu();return}s=!0,o&&Du(),k(this,Zt)!==null&&Wn(k(this,Zt),()=>{Y(this,Zt,null)}),nt(this,bt,Qs).call(this,()=>{nt(this,bt,_r).call(this)})},a=l=>{try{o=!0,n==null||n(l,r),o=!1}catch(c){an(c,k(this,Wt)&&k(this,Wt).parent)}i&&Y(this,Zt,nt(this,bt,Qs).call(this,()=>{try{return ae(()=>{var c=Q;c.b=this,c.f|=hr,i(k(this,se),()=>l,()=>r)})}catch(c){return an(c,k(this,Wt).parent),null}}))};pn(()=>{var l;try{l=this.transform_error(t)}catch(c){an(c,k(this,Wt)&&k(this,Wt).parent);return}l!==null&&typeof l=="object"&&typeof l.then=="function"?l.then(a,c=>an(c,k(this,Wt)&&k(this,Wt).parent)):a(l)})};function nf(e,t,n,i){const s=Si()?Xr:kc;var o=e.filter(f=>!f.settled);if(n.length===0&&o.length===0){i(t.map(s));return}var r=Q,a=sf(),l=o.length===1?o[0].promise:o.length>1?Promise.all(o.map(f=>f.promise)):null;function c(f){if((r.f&xe)===0){a();try{i(f)}catch(d){an(d,r)}uo()}}var h=wc();if(n.length===0){l.then(()=>c(t.map(s))).finally(h);return}function u(){Promise.all(n.map(f=>of(f))).then(f=>c([...t.map(s),...f])).catch(f=>an(f,r)).finally(h)}l?l.then(()=>{a(),u(),uo()}):u()}function sf(){var e=Q,t=J,n=_t,i=N;return function(o=!0){Ie(e),fe(t),mi(n),o&&(e.f&xe)===0&&(i==null||i.activate(),i==null||i.apply())}}function uo(e=!0){Ie(null),fe(null),mi(null),e&&(N==null||N.deactivate())}function wc(){var e=Q,t=e.b,n=N,i=t.is_rendered();return t.update_pending_count(1,n),n.increment(i,e),()=>{t.update_pending_count(-1,n),n.decrement(i,e)}}function Xr(e){var t=Ot|Ct;return Q!==null&&(Q.f|=Mi),{ctx:_t,deps:null,effects:null,equals:uc,f:t,fn:e,reactions:null,rv:0,v:Mt,wv:0,parent:Q,ac:null}}const Ms=Symbol("obsolete");function of(e,t,n){let i=Q;i===null&&xu();var s=void 0,o=Gn(Mt),r=!J,a=new Set;return _f(()=>{var d;var l=Q,c=oc();s=c.promise;try{Promise.resolve(e()).then(c.resolve,g=>{g!==Do&&c.reject(g)}).finally(uo)}catch(g){c.reject(g),uo()}var h=N;if(r){if((l.f&Qn)!==0)var u=wc();if(i.b.is_rendered())(d=h.async_deriveds.get(l))==null||d.reject(Ms);else for(const g of a.values())g.reject(Ms);a.add(c),h.async_deriveds.set(l,c)}const f=(g,m=void 0)=>{u==null||u(),a.delete(c),m!==Ms&&(h.activate(),m?(o.f|=gn,vi(o,m)):((o.f&gn)!==0&&(o.f^=gn),vi(o,g)),h.deactivate())};c.promise.then(f,g=>f(null,g||"unknown"))}),Lc(()=>{for(const l of a)l.reject(Ms)}),new Promise(l=>{function c(h){function u(){h===s?l(o):c(s)}h.then(u,u)}c(s)})}function it(e){const t=Xr(e);return jc(t),t}function kc(e){const t=Xr(e);return t.equals=fc,t}function rf(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n0&&!Pc&&cf()}return t}function cf(){Pc=!1;for(const e of fo){(e.f&St)!==0&&mt(e,Re);let t;try{t=_s(e)}catch{t=!0}t&&_i(e)}fo.clear()}function Yi(e){L(e,e.v+1)}function Cc(e,t,n){var i=e.reactions;if(i!==null)for(var s=Si(),o=i.length,r=0;r{if(Hn===o)return a();var l=J,c=Hn;fe(null),ka(o);var h=a();return fe(l),ka(c),h};return i&&n.set("length",B(e.length)),new Proxy(e,{defineProperty(a,l,c){(!("value"in c)||c.configurable===!1||c.enumerable===!1||c.writable===!1)&&Pu();var h=n.get(l);return h===void 0?r(()=>{var u=B(c.value);return n.set(l,u),u}):L(h,c.value,!0),!0},deleteProperty(a,l){var c=n.get(l);if(c===void 0){if(l in a){const h=r(()=>B(Mt));n.set(l,h),Yi(s)}}else L(c,Mt),Yi(s);return!0},get(a,l,c){var d;if(l===qs)return e;var h=n.get(l),u=l in a;if(h===void 0&&(!u||(d=Vi(a,l))!=null&&d.writable)&&(h=r(()=>{var g=Tt(u?a[l]:Mt),m=B(g);return m}),n.set(l,h)),h!==void 0){var f=b(h);return f===Mt?void 0:f}return Reflect.get(a,l,c)},getOwnPropertyDescriptor(a,l){var c=Reflect.getOwnPropertyDescriptor(a,l);if(c&&"value"in c){var h=n.get(l);h&&(c.value=b(h))}else if(c===void 0){var u=n.get(l),f=u==null?void 0:u.v;if(u!==void 0&&f!==Mt)return{enumerable:!0,configurable:!0,value:f,writable:!0}}return c},has(a,l){var f;if(l===qs)return!0;var c=n.get(l),h=c!==void 0&&c.v!==Mt||Reflect.has(a,l);if(c!==void 0||Q!==null&&(!h||(f=Vi(a,l))!=null&&f.writable)){c===void 0&&(c=r(()=>{var d=h?Tt(a[l]):Mt,g=B(d);return g}),n.set(l,c));var u=b(c);if(u===Mt)return!1}return h},set(a,l,c,h){var x;var u=n.get(l),f=l in a;if(i&&l==="length")for(var d=c;dB(Mt)),n.set(d+"",g))}if(u===void 0)(!f||(x=Vi(a,l))!=null&&x.writable)&&(u=r(()=>B(void 0)),L(u,Tt(c)),n.set(l,u));else{f=u.v!==Mt;var m=r(()=>Tt(c));L(u,m)}var p=Reflect.getOwnPropertyDescriptor(a,l);if(p!=null&&p.set&&p.set.call(h,c),!f){if(i&&typeof l=="string"){var v=n.get("length"),_=Number(l);Number.isInteger(_)&&_>=v.v&&L(v,_+1)}Yi(s)}return!0},ownKeys(a){b(s);var l=Reflect.ownKeys(a).filter(u=>{var f=n.get(u);return f===void 0||f.v!==Mt});for(var[c,h]of n)h.v!==Mt&&!(c in a)&&l.push(c);return l},setPrototypeOf(){Cu()}})}var yr,Ac,Dc,Tc;function hf(){if(yr===void 0){yr=window,Ac=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;Dc=Vi(t,"firstChild").get,Tc=Vi(t,"nextSibling").get,_a(e)&&(e[fr]=void 0,e[$s]=null,e[dr]=void 0,e.__e=void 0),_a(n)&&(n[Ri]=void 0)}}function Ke(e=""){return document.createTextNode(e)}function ln(e){return Dc.call(e)}function ms(e){return Tc.call(e)}function C(e,t){return ln(e)}function rt(e,t=!1){{var n=ln(e);return n instanceof Comment&&n.data===""?ms(n):n}}function T(e,t=1,n=!1){let i=e;for(;t--;)i=ms(i);return i}function uf(e){e.textContent=""}function Oc(){return!1}function Ec(e,t,n){return document.createElementNS(t??hc,e,void 0)}let ya=!1;function ff(){ya||(ya=!0,document.addEventListener("reset",e=>{Promise.resolve().then(()=>{var t;if(!e.defaultPrevented)for(const n of e.target.elements)(t=n[Ks])==null||t.call(n)})},{capture:!0}))}function To(e){var t=J,n=Q;fe(null),Ie(null);try{return e()}finally{fe(t),Ie(n)}}function df(e,t,n,i=n){e.addEventListener(t,()=>To(n));const s=e[Ks];s?e[Ks]=()=>{s(),i(!0)}:e[Ks]=()=>i(!0),ff()}function gf(e){Q===null&&(J===null&&Mu(),ku()),_n&&wu()}function pf(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function Je(e,t){var n=Q;n!==null&&(n.f&Bt)!==0&&(e|=Bt);var i={ctx:_t,deps:null,nodes:null,f:e|Ct|ce,first:null,fn:t,last:null,next:null,parent:n,b:n&&n.b,prev:null,teardown:null,wv:0,ac:null};N==null||N.register_created_effect(i);var s=i;if((e&pi)!==0)oi!==null?oi.push(i):Kn.ensure().schedule(i);else if(t!==null){try{_i(i)}catch(r){throw Ut(i),r}s.deps===null&&s.teardown===null&&s.nodes===null&&s.first===s.last&&(s.f&Mi)===0&&(s=s.first,(e&ve)!==0&&(e&qn)!==0&&s!==null&&(s.f|=qn))}if(s!==null&&(s.parent=n,n!==null&&pf(s,n),J!==null&&(J.f&Ot)!==0&&(e&vn)===0)){var o=J;(o.effects??(o.effects=[])).push(s)}return i}function $r(){return J!==null&&!_e}function Lc(e){const t=Je(Ao,null);return mt(t,St),t.teardown=e,t}function Vn(e){gf();var t=Q.f,n=!J&&(t&ke)!==0&&(t&Qn)===0;if(n){var i=_t;(i.e??(i.e=[])).push(e)}else return Rc(e)}function Rc(e){return Je(pi|_u,e)}function mf(e){Kn.ensure();const t=Je(vn|Mi,e);return(n={})=>new Promise(i=>{n.outro?Wn(t,()=>{Ut(t),i(void 0)}):(Ut(t),i(void 0))})}function vf(e){return Je(pi,e)}function _f(e){return Je(ai|Mi,e)}function Kr(e,t=0){return Je(Ao|t,e)}function G(e,t=[],n=[],i=[]){nf(i,t,n,s=>{Je(Ao,()=>e(...s.map(b)))})}function vs(e,t=0){var n=Je(ve|t,e);return n}function ae(e){return Je(ke|Mi,e)}function Ic(e){var t=e.teardown;if(t!==null){const n=_n,i=J;wa(!0),fe(null);try{t.call(null)}finally{wa(n),fe(i)}}}function Gr(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){const s=n.ac;s!==null&&To(()=>{s.abort(Do)});var i=n.next;(n.f&vn)!==0?n.parent=null:Ut(n,t),n=i}}function bf(e){for(var t=e.first;t!==null;){var n=t.next;(t.f&ke)===0&&Ut(t),t=n}}function Ut(e,t=!0){var n=!1;(t||(e.f&vu)!==0)&&e.nodes!==null&&e.nodes.end!==null&&(Fc(e.nodes.start,e.nodes.end),n=!0),mt(e,ur),Gr(e,t&&!n),ts(e,0);var i=e.nodes&&e.nodes.t;if(i!==null)for(const o of i)o.stop();Ic(e),e.f^=ur,e.f|=xe;var s=e.parent;s!==null&&s.first!==null&&zc(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=e.b=null}function Fc(e,t){for(;e!==null;){var n=e===t?null:ms(e);e.remove(),e=n}}function zc(e){var t=e.parent,n=e.prev,i=e.next;n!==null&&(n.next=i),i!==null&&(i.prev=n),t!==null&&(t.first===e&&(t.first=i),t.last===e&&(t.last=n))}function Wn(e,t,n=!0){var i=[];Nc(e,i,!0);var s=()=>{n&&Ut(e),t&&t()},o=i.length;if(o>0){var r=()=>--o||s();for(var a of i)a.out(r)}else s()}function Nc(e,t,n){if((e.f&Bt)===0){e.f^=Bt;var i=e.nodes&&e.nodes.t;if(i!==null)for(const a of i)(a.is_global||n)&&t.push(a);for(var s=e.first;s!==null;){var o=s.next;if((s.f&vn)===0){var r=(s.f&qn)!==0||(s.f&ke)!==0&&(e.f&ve)!==0;Nc(s,t,r?n:!1)}s=o}}}function Jr(e){Bc(e,!0)}function Bc(e,t){if((e.f&Bt)!==0){e.f^=Bt,(e.f&St)===0&&(mt(e,Ct),Kn.ensure().schedule(e));for(var n=e.first;n!==null;){var i=n.next,s=(n.f&qn)!==0||(n.f&ke)!==0;Bc(n,s?t:!1),n=i}var o=e.nodes&&e.nodes.t;if(o!==null)for(const r of o)(r.is_global||t)&&r.in()}}function Zr(e,t){if(e.nodes)for(var n=e.nodes.start,i=e.nodes.end;n!==null;){var s=n===i?null:ms(n);t.append(n),n=s}}let to=!1,_n=!1;function wa(e){_n=e}let J=null,_e=!1;function fe(e){J=e}let Q=null;function Ie(e){Q=e}let he=null;function jc(e){J!==null&&(he===null?he=[e]:he.push(e))}let Ht=null,Kt=0,ie=null;function xf(e){ie=e}let Vc=1,Tn=0,Hn=Tn;function ka(e){Hn=e}function Wc(){return++Vc}function _s(e){var t=e.f;if((t&Ct)!==0)return!0;if(t&Ot&&(e.f&=~$n),(t&Re)!==0){for(var n=e.deps,i=n.length,s=0;se.wv)return!0}(t&ce)!==0&&Dt===null&&mt(e,St)}return!1}function Hc(e,t,n=!0){var i=e.reactions;if(i!==null&&!(he!==null&&Bn.call(he,e)))for(var s=0;s{e.ac.abort(Do)}),e.ac=null);try{e.f|=ho;var h=e.fn,u=h();e.f|=Qn;var f=e.deps,d=N==null?void 0:N.is_fork;if(Ht!==null){var g;if(d||ts(e,Kt),f!==null&&Kt>0)for(f.length=Kt+Ht.length,g=0;gn==null?void 0:n.call(this,o))}return e.startsWith("pointer")||e.startsWith("touch")||e==="wheel"?pn(()=>{t.addEventListener(e,s,i)}):t.addEventListener(e,s,i),s}function $c(e,t,n,i,s){var o={capture:i,passive:s},r=Sf(e,t,n,o);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&Lc(()=>{t.removeEventListener(e,r,o)})}function ut(e,t,n){(t[On]??(t[On]={}))[e]=n}function Me(e){for(var t=0;t{throw _});throw f}}finally{e[On]=t,delete e.currentTarget,fe(h),Ie(u)}}}var tc;const Ho=((tc=globalThis==null?void 0:globalThis.window)==null?void 0:tc.trustedTypes)&&globalThis.window.trustedTypes.createPolicy("svelte-trusted-html",{createHTML:e=>e});function Pf(e){return(Ho==null?void 0:Ho.createHTML(e))??e}function Cf(e){var t=Ec("template");return t.innerHTML=Pf(e.replaceAll("","")),t.content}function bi(e,t){var n=Q;n.nodes===null&&(n.nodes={start:e,end:t,a:null,t:null})}function R(e,t){var n=(t&Ru)!==0,i=(t&Iu)!==0,s,o=!e.startsWith("");return()=>{s===void 0&&(s=Cf(o?e:""+e),n||(s=ln(s)));var r=i||Ac?document.importNode(s,!0):s.cloneNode(!0);if(n){var a=ln(r),l=r.lastChild;bi(a,l)}else bi(r,r);return r}}function yt(e=""){{var t=Ke(e+"");return bi(t,t),t}}function zt(){var e=document.createDocumentFragment(),t=document.createComment(""),n=Ke();return e.append(t,n),bi(t,n),e}function A(e,t){e!==null&&e.before(t)}function q(e,t){var n=t==null?"":typeof t=="object"?`${t}`:t;n!==(e[Ri]??(e[Ri]=e.nodeValue))&&(e[Ri]=n,e.nodeValue=`${n}`)}function Af(e,t){return Df(e,t)}const Ss=new Map;function Df(e,{target:t,anchor:n,props:i={},events:s,context:o,intro:r=!0,transformError:a}){hf();var l=void 0,c=mf(()=>{var h=n??t.appendChild(Ke());Ju(h,{pending:()=>{}},d=>{at({});var g=_t;o&&(g.c=o),s&&(i.$$events=s),l=e(d,i)||{},lt()},a);var u=new Set,f=d=>{for(var g=0;g{var p;for(var d of u)for(const v of[t,document]){var g=Ss.get(v),m=g.get(d);--m==0?(v.removeEventListener(d,kr),g.delete(d),g.size===0&&Ss.delete(v)):g.set(d,m)}wr.delete(f),h!==n&&((p=h.parentNode)==null||p.removeChild(h))}});return Tf.set(l,c),l}let Tf=new WeakMap;var me,Te,Qt,Nn,ds,gs,Po;class Qr{constructor(t,n=!0){D(this,"anchor");X(this,me,new Map);X(this,Te,new Map);X(this,Qt,new Map);X(this,Nn,new Set);X(this,ds,!0);X(this,gs,t=>{if(k(this,me).has(t)){var n=k(this,me).get(t),i=k(this,Te).get(n);if(i)Jr(i),k(this,Nn).delete(n);else{var s=k(this,Qt).get(n);s&&(k(this,Te).set(n,s.effect),k(this,Qt).delete(n),s.fragment.lastChild.remove(),this.anchor.before(s.fragment),i=s.effect)}for(const[o,r]of k(this,me)){if(k(this,me).delete(o),o===t)break;const a=k(this,Qt).get(r);a&&(Ut(a.effect),k(this,Qt).delete(r))}for(const[o,r]of k(this,Te)){if(o===n||k(this,Nn).has(o))continue;const a=()=>{if(Array.from(k(this,me).values()).includes(o)){var c=document.createDocumentFragment();Zr(r,c),c.append(Ke()),k(this,Qt).set(o,{effect:r,fragment:c})}else Ut(r);k(this,Nn).delete(o),k(this,Te).delete(o)};k(this,ds)||!i?(k(this,Nn).add(o),Wn(r,a,!1)):a()}}});X(this,Po,t=>{k(this,me).delete(t);const n=Array.from(k(this,me).values());for(const[i,s]of k(this,Qt))n.includes(i)||(Ut(s.effect),k(this,Qt).delete(i))});this.anchor=t,Y(this,ds,n)}ensure(t,n){var i=N,s=Oc();if(n&&!k(this,Te).has(t)&&!k(this,Qt).has(t))if(s){var o=document.createDocumentFragment(),r=Ke();o.append(r),k(this,Qt).set(t,{effect:ae(()=>n(r)),fragment:o})}else k(this,Te).set(t,ae(()=>n(this.anchor)));if(k(this,me).set(i,t),s){for(const[a,l]of k(this,Te))a===t?i.unskip_effect(l):i.skip_effect(l);for(const[a,l]of k(this,Qt))a===t?i.unskip_effect(l.effect):i.skip_effect(l.effect);i.oncommit(k(this,gs)),i.ondiscard(k(this,Po))}else k(this,gs).call(this,i)}}me=new WeakMap,Te=new WeakMap,Qt=new WeakMap,Nn=new WeakMap,ds=new WeakMap,gs=new WeakMap,Po=new WeakMap;function tt(e,t,n=!1){var i=new Qr(e),s=n?qn:0;function o(r,a){i.ensure(r,a)}vs(()=>{var r=!1;t((a,l=0)=>{r=!0,o(l,a)}),r||o(-1,null)},s)}const Of=Symbol("NaN");function Ef(e,t,n){var i=new Qr(e),s=!Si();vs(()=>{var o=t();o!==o&&(o=Of),s&&o!==null&&typeof o=="object"&&(o={}),i.ensure(o,n)})}function Lf(e,t){return t}function Rf(e,t,n){for(var i=[],s=t.length,o,r=t.length,a=0;a{if(o){if(o.pending.delete(u),o.done.add(u),o.pending.size===0){var f=e.outrogroups;Mr(e,Co(o.done)),f.delete(o),f.size===0&&(e.outrogroups=null)}}else r-=1},!1)}if(r===0){var l=i.length===0&&n!==null;if(l){var c=n,h=c.parentNode;uf(h),h.append(c),e.items.clear()}Mr(e,t,!l)}else o={pending:new Set(t),done:new Set},(e.outrogroups??(e.outrogroups=new Set)).add(o)}function Mr(e,t,n=!0){var i;if(e.pending.size>0){i=new Set;for(const r of e.pending.values())for(const a of r)i.add(e.items.get(a).e)}for(var s=0;s{var x=n();return ic(x)?x:x==null?[]:Co(x)}),f,d=new Map,g=!0;function m(x){(_.effect.f&xe)===0&&(_.pending.delete(x),_.fallback=h,If(_,f,r,t,i),h!==null&&(f.length===0?(h.f&Ee)===0?Jr(h):(h.f^=Ee,Fi(h,null,r)):Wn(h,()=>{h=null})))}function p(x){_.pending.delete(x)}var v=vs(()=>{f=b(u);for(var x=f.length,y=new Set,w=N,S=Oc(),M=0;Mo(r)):(h=ae(()=>o(Sa??(Sa=Ke()))),h.f|=Ee)),x>y.size&&yu(),!g)if(d.set(w,y),S){for(const[j,O]of a)y.has(j)||w.skip_effect(O.e);w.oncommit(m),w.ondiscard(p)}else m(w);b(u)}),_={effect:v,items:a,pending:d,outrogroups:null,fallback:h};g=!1}function Ci(e){for(;e!==null&&(e.f&ke)===0;)e=e.next;return e}function If(e,t,n,i,s){var I,j,O,F,V,z,H,K,et;var o=(i&Eu)!==0,r=t.length,a=e.items,l=Ci(e.effect.first),c,h=null,u,f=[],d=[],g,m,p,v;if(o)for(v=0;v0){var E=(i&cc)!==0&&r===0?n:null;if(o){for(v=0;v{var U,kt;if(u!==void 0)for(p of u)(kt=(U=p.nodes)==null?void 0:U.a)==null||kt.apply()})}function Ff(e,t,n,i,s,o,r,a){var l=(r&Tu)!==0?(r&Lu)===0?lf(n,!1,!1):Gn(n):null,c=(r&Ou)!==0?Gn(s):null;return{v:l,i:c,e:ae(()=>(o(t,l??n,c??s,a),()=>{e.delete(i)}))}}function Fi(e,t,n){if(e.nodes)for(var i=e.nodes.start,s=e.nodes.end,o=t&&(t.f&Ee)===0?t.nodes.start:n;i!==null;){var r=ms(i);if(o.before(i),i===s)return;i=r}}function en(e,t,n){t===null?e.effect.first=n:t.next=n,n===null?e.effect.last=t:n.prev=t}function Kc(e,t,n=!1,i=!1,s=!1,o=!1){var r=e,a="";if(n)var l=e;G(()=>{var c=Q;if(a!==(a=t()??"")){if(n){c.nodes=null,l.innerHTML=a,a!==""&&bi(ln(l),l.lastChild);return}if(c.nodes!==null&&(Fc(c.nodes.start,c.nodes.end),c.nodes=null),a!==""){var h=i?Fu:s?zu:void 0,u=Ec(i?"svg":s?"math":"template",h);u.innerHTML=a;var f=i||s?u:u.content;if(bi(ln(f),f.lastChild),i||s)for(;ln(f);)r.before(ln(f));else r.before(f)}}})}function bs(e,t,...n){var i=new Qr(e);vs(()=>{const s=t()??null;i.ensure(s,s&&(o=>s(o,...n)))},qn)}const Pa=[...` -\r\f \v\uFEFF`];function zf(e,t,n){var i=e==null?"":""+e;if(t&&(i=i?i+" "+t:t),n){for(var s of Object.keys(n))if(n[s])i=i?i+" "+s:s;else if(i.length)for(var o=s.length,r=0;(r=i.indexOf(s,r))>=0;){var a=r+o;(r===0||Pa.includes(i[r-1]))&&(a===i.length||Pa.includes(i[a]))?i=(r===0?"":i.substring(0,r))+i.substring(a+1):r=a}}return i===""?null:i}function Nf(e,t){return e==null?null:String(e)}function ue(e,t,n,i,s,o){var r=e[fr];if(r!==n||r===void 0){var a=zf(n,i,o);a==null?e.removeAttribute("class"):e.className=a,e[fr]=n}else if(o&&s!==o)for(var l in o){var c=!!o[l];(s==null||c!==!!s[l])&&e.classList.toggle(l,c)}return o}function Gc(e,t,n,i){var s=e[dr];if(s!==t){var o=Nf(t);o==null?e.removeAttribute("style"):e.style.cssText=o,e[dr]=t}return i}const Bf=Symbol("is custom element"),jf=Symbol("is html");function Pt(e,t,n,i){var s=Vf(e);s[t]!==(s[t]=n)&&(t==="loading"&&(e[bu]=n),n==null?e.removeAttribute(t):typeof n!="string"&&Wf(e).includes(t)?e[t]=n:e.setAttribute(t,n))}function Vf(e){return e[$s]??(e[$s]={[Bf]:e.nodeName.includes("-"),[jf]:e.namespaceURI===hc})}var Ca=new Map;function Wf(e){var t=e.getAttribute("is")||e.nodeName,n=Ca.get(t);if(n)return n;Ca.set(t,n=[]);for(var i,s=e,o=Element.prototype;o!==s;){i=fu(s);for(var r in i)i[r].set&&r!=="innerHTML"&&r!=="textContent"&&r!=="innerText"&&n.push(r);s=sc(s)}return n}function es(e,t,n=t){var i=new WeakSet;df(e,"input",async s=>{var o=s?e.defaultValue:e.value;if(o=Yo(e)?Uo(o):o,n(o),N!==null&&i.add(N),await wf(),o!==(o=t())){var r=e.selectionStart,a=e.selectionEnd,l=e.value.length;if(e.value=o??"",a!==null){var c=e.value.length;r===a&&a===l&&c>l?(e.selectionStart=c,e.selectionEnd=c):(e.selectionStart=r,e.selectionEnd=Math.min(a,c))}}}),Ge(t)==null&&e.value&&(n(Yo(e)?Uo(e.value):e.value),N!==null&&i.add(N)),Kr(()=>{var s=t();if(e===document.activeElement){var o=N;if(i.has(o))return}Yo(e)&&s===Uo(e.value)||e.type==="date"&&!s&&!e.value||s!==e.value&&(e.value=s??"")})}function Yo(e){var t=e.type;return t==="number"||t==="range"}function Uo(e){return e===""?null:+e}function Xo(e,t){return e===t||(e==null?void 0:e[qs])===t}function Oo(e={},t,n,i){var s=_t.r,o=Q;return vf(()=>{var r,a;return Kr(()=>{r=a,a=[],Ge(()=>{Xo(n(...a),e)||(t(e,...a),r&&Xo(n(...r),e)&&t(null,...r))})}),()=>{let l=o;for(;l!==s&&l.parent!==null&&l.parent.f&ur;)l=l.parent;const c=()=>{a&&Xo(n(...a),e)&&t(null,...a)},h=l.teardown;l.teardown=()=>{c(),h==null||h()}}}),e}function Xt(e,t,n,i){var s=i,o=!0,r=()=>(o&&(o=!1,s=i),s),a;a=e[t],a===void 0&&i!==void 0&&(a=r());var l;return l=()=>{var c=e[t];return c===void 0?r():(o=!0,c)},l}function Ze(e){_t===null&&lc(),ps&&_t.l!==null?Hf(_t).m.push(e):Vn(()=>{const t=Ge(e);if(typeof t=="function")return t})}function Jc(e){_t===null&&lc(),Ze(()=>()=>Ge(e))}function Hf(e){var t=e.l;return t.u??(t.u={a:[],b:[],m:[]})}const Yf="5";var ec;typeof window<"u"&&((ec=window.__svelte??(window.__svelte={})).v??(ec.v=new Set)).add(Yf);const Aa="serena-dashboard-theme";function Uf(){let e=B("light");function t(n){L(e,n,!0),document.documentElement.setAttribute("data-theme",n)}return{get current(){return b(e)},init(){var s;const n=localStorage.getItem(Aa),i=(s=window.matchMedia)==null?void 0:s.call(window,"(prefers-color-scheme: dark)").matches;t(n??(i?"dark":"light"))},toggle(){const n=b(e)==="dark"?"light":"dark";t(n),localStorage.setItem(Aa,n)}}}const Yn=Uf();var Xf=R('');function qf(e,t){at(t,!0);const n=it(()=>Yn.current==="dark"?"Switch to light theme":"Switch to dark theme");var i=Xf(),s=C(i),o=C(s);G(()=>{Pt(i,"aria-label",b(n)),Pt(i,"title",b(n)),q(o,Yn.current==="dark"?"☀":"🌙")}),ut("click",i,()=>Yn.toggle()),A(e,i),lt()}Me(["click"]);const $f="https://oraios-software.de/serena-banners/manifest.php";function Kf(e,t){return e[t]??[]}function Gf(e,t){return t==="dark"?e.image_dark??e.image:e.image}async function Jf(){try{const e=await fetch($f);return e.ok?await e.json():{}}catch{return{}}}var Zf=R('
              ');function Zc(e,t){at(t,!0);let n=B(Tt([])),i=B(0),s=null;async function o(){L(n,Kf(await Jf(),t.target),!0),b(n).length&&L(i,Math.floor(Math.random()*b(n).length),!0),b(n).length>1&&(s=setInterval(()=>{L(i,(b(i)+1)%b(n).length)},6e3))}Ze(()=>{o()}),Jc(()=>{s&&clearInterval(s)});const r=it(()=>b(n)[b(i)]);var a=zt(),l=rt(a);{var c=h=>{var u=Zf(),f=C(u),d=C(f);G(g=>{ue(u,1,`banner ${t.target??""}`,"svelte-1lo7lbo"),Pt(f,"href",b(r).link),Pt(d,"src",g),Pt(d,"alt",b(r).alt??"")},[()=>Gf(b(r),Yn.current)]),A(h,u)};tt(l,h=>{b(r)&&h(c)})}A(e,a),lt()}var Qf=R('
              ');function td(e,t){at(t,!0);const n=it(()=>Yn.current==="dark"?"serena-logo-dark-mode.svg":"serena-logo.svg");var i=Qf(),s=C(i),o=C(s),r=C(o),a=T(o,2),l=C(a);Zc(l,{target:"platinum"});var c=T(s,2),h=C(c),u=C(h);qf(u,{});var f=T(u,2),d=T(h,2),g=C(d);let m;var p=T(g,2);let v;var _=T(p,2);let x;G(()=>{Pt(r,"src",b(n)),m=ue(g,1,"header-tab svelte-xjepwq",null,m,{active:t.active==="overview"}),Pt(g,"aria-current",t.active==="overview"?"page":void 0),v=ue(p,1,"header-tab svelte-xjepwq",null,v,{active:t.active==="logs"}),Pt(p,"aria-current",t.active==="logs"?"page":void 0),x=ue(_,1,"header-tab svelte-xjepwq",null,x,{active:t.active==="stats"}),Pt(_,"aria-current",t.active==="stats"?"page":void 0)}),ut("click",f,()=>t.onshutdown()),ut("click",g,()=>t.onnavigate("overview")),ut("click",p,()=>t.onnavigate("logs")),ut("click",_,()=>t.onnavigate("stats")),A(e,i),lt()}Me(["click"]);function Ps(e,t){let n=null,i=!1;const s=async()=>{if(!i){i=!0;try{await e()}finally{i=!1}}};return{start(){n||(s(),n=setInterval(()=>void s(),t))},stop(){n&&(clearInterval(n),n=null),i=!1}}}function ed(e){switch(e){case"overview":return["config","queued","last"];case"logs":return["logs"];case"stats":return[]}}class nd extends Error{constructor(t,n){super(n),this.status=t}}async function ta(e){if(!e.ok)throw new nd(e.status,`HTTP ${e.status} for ${e.url}`);return await e.json()}async function Qe(e){return ta(await fetch(e))}async function de(e,t){return ta(await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t??{})}))}async function id(e){return ta(await fetch(e,{method:"PUT"}))}const sd=()=>Qe("/get_config_overview"),od=e=>de("/get_log_messages",{start_idx:e}),rd=()=>de("/clear_logs"),ad=()=>Qe("/get_tool_names"),ld=()=>Qe("/get_tool_stats"),cd=()=>de("/clear_tool_stats"),hd=()=>Qe("/get_token_count_estimator_name"),ud=()=>id("/shutdown"),fd=()=>Qe("/get_available_languages"),dd=e=>de("/add_language",{language:e}),gd=e=>de("/remove_language",{language:e}),pd=e=>de("/get_memory",{memory_name:e}),Qc=(e,t)=>de("/save_memory",{memory_name:e,content:t}),md=e=>de("/delete_memory",{memory_name:e}),vd=(e,t)=>de("/rename_memory",{old_name:e,new_name:t}),_d=()=>Qe("/get_serena_config"),bd=e=>de("/save_serena_config",{content:e}),xd=()=>Qe("/queued_task_executions"),yd=e=>de("/cancel_task_execution",{task_id:e}),wd=()=>Qe("/last_execution"),kd=()=>Qe("/fetch_unread_news"),Md=e=>de("/mark_news_snippet_as_read",{news_snippet_id:e});function Sd(){let e=B(null),t="";return{get data(){return b(e)},async poll(){const n=await sd(),i=JSON.stringify(n);i!==t&&(L(e,n,!0),t=i)}}}const go=Sd();async function ye(e){try{const t=await e();return t&&t.status==="error"?{ok:!1,message:t.message??"Request failed",data:t}:{ok:!0,data:t}}catch(t){return{ok:!1,message:t instanceof Error?t.message:"Request failed"}}}function Pd(){let e=B(Tt([])),t=B(null),n=B(Tt([])),i=B("");const s=new Set;return{get queued(){return b(e)},get last(){return b(t)},get cancelled(){return b(n)},get cancelError(){return b(i)},clearCancelError(){L(i,"")},async pollQueued(){L(e,(await xd()).queued_executions,!0)},async pollLast(){const o=(await wd()).last_execution;L(t,o&&o.logged?o:null,!0)},async cancel(o){if(s.has(o.task_id))return{ok:!1};s.add(o.task_id),L(i,"");try{const r=await ye(()=>yd(o.task_id));return r.ok?(b(n).some(a=>a.task_id===o.task_id)||L(n,[...b(n),o],!0),r):(L(i,r.message??"Failed to cancel execution",!0),r)}finally{s.delete(o.task_id)}}}}const Oe=Pd();function Cd(){let e=B(Tt([])),t=B(0),n=B(null);return{get lines(){return b(e)},get activeProject(){return b(n)},async poll(){const i=await od(b(t));i.messages.length&&L(e,[...b(e),...i.messages],!0),L(t,i.max_idx,!0),L(n,i.active_project,!0)},async clear(){await rd(),L(e,[],!0),L(t,0)}}}const eo=Cd();function Ad(e){return e?`${e} – Serena Dashboard`:"Serena Dashboard"}var Dd=R('
              '),Td=R('

              ');function po(e,t){let n=Xt(t,"open",3,!1),i=B(Tt(n()));function s(){L(i,!b(i))}var o=Td(),r=C(o),a=C(r),l=C(a),c=C(l),h=T(l,2);let u;var f=T(r,2);{var d=g=>{var m=Dd(),p=C(m);bs(p,()=>t.children),A(g,m)};tt(f,g=>{b(i)&&g(d)})}G(()=>{Pt(a,"aria-expanded",b(i)),q(c,t.title),u=ue(h,1,"toggle-icon svelte-oy8gea",null,u,{open:b(i)})}),ut("click",a,s),A(e,o)}Me(["click"]);var Od=R("");function qt(e,t){let n=Xt(t,"variant",3,"primary"),i=Xt(t,"disabled",3,!1);var s=Od(),o=C(s);bs(o,()=>t.children),G(()=>{ue(s,1,`btn ${n()??""}`,"svelte-18f749u"),s.disabled=i()}),ut("click",s,function(...r){var a;(a=t.onclick)==null||a.apply(this,r)}),A(e,s)}Me(["click"]);var Ed=R(" "),Ld=R(''),Rd=R(' '),Id=R(' '),Fd=R(" ",1),zd=R(' '),Nd=R('
              '),Bd=R('
              '),jd=R('
              '),Vd=R('
              Version Active Project Languages Context Active Modes File Encoding
              ');function Wd(e,t){at(t,!0);var n=Vd(),i=C(n),s=T(C(i),2),o=C(s),r=T(s,4),a=C(r);{var l=O=>{var F=Ed(),V=C(F);G(()=>{Pt(F,"title",`Project configuration in ${t.data.active_project.path??""}/.serena/project.yml`),q(V,t.data.active_project.name)}),A(O,F)},c=O=>{var F=yt();G(()=>{var V;return q(F,((V=t.data.active_project)==null?void 0:V.name)??"None")}),A(O,F)};tt(a,O=>{var F,V;(F=t.data.active_project)!=null&&F.name&&((V=t.data.active_project)!=null&&V.path)?O(l):O(c,-1)})}var h=T(r,4),u=C(h);{var f=O=>{var F=yt("Using JetBrains backend");A(O,F)},d=O=>{var F=Id(),V=C(F);le(V,16,()=>t.data.languages,K=>K,(K,et)=>{var U=Rd(),kt=C(U),te=T(kt);{var ee=Lt=>{var $t=Ld();G(()=>Pt($t,"aria-label",`Remove ${et??""}`)),ut("click",$t,()=>t.onremovelanguage(et)),A(Lt,$t)};tt(te,Lt=>{t.data.languages.length>1&&Lt(ee)})}G(()=>q(kt,`${et??""} `)),A(K,U)});var z=T(V,2);{var H=K=>{qt(K,{variant:"secondary",get onclick(){return t.onaddlanguage},children:(et,U)=>{var kt=yt("Add Language");A(et,kt)},$$slots:{default:!0}})};tt(z,K=>{var et;(et=t.data.active_project)!=null&&et.name&&K(H)})}A(O,F)};tt(u,O=>{t.data.jetbrains_mode?O(f):O(d,-1)})}var g=T(h,4),m=C(g),p=C(m),v=T(g,4),_=C(v);{var x=O=>{var F=zt(),V=rt(F);le(V,19,()=>t.data.modes,z=>z.name,(z,H,K)=>{var et=Fd(),U=rt(et),kt=C(U),te=T(U);{var ee=Lt=>{var $t=yt(",");A(Lt,$t)};tt(te,Lt=>{b(K){Pt(U,"title",b(H).path),q(kt,b(H).name)}),A(z,et)}),A(O,F)},y=O=>{var F=yt("None");A(O,F)};tt(_,O=>{t.data.modes.length?O(x):O(y,-1)})}var w=T(v,4),S=C(w),M=T(i,2);po(M,{get title(){return`Active Tools (${t.data.active_tools.length??""})`},children:(O,F)=>{var V=Nd();le(V,20,()=>t.data.active_tools,z=>z,(z,H)=>{var K=zd(),et=C(K);G(()=>q(et,H)),A(z,K)}),A(O,V)},$$slots:{default:!0}});var P=T(M,2);{var E=O=>{po(O,{get title(){return`Memories (${t.data.available_memories.length??""})`},children:(F,V)=>{var z=jd(),H=C(z);le(H,16,()=>t.data.available_memories,et=>et,(et,U)=>{var kt=Bd(),te=C(kt),ee=C(te),Lt=T(te,2);G(()=>{q(ee,U),Pt(Lt,"aria-label",`Delete memory ${U??""}`)}),ut("click",te,()=>t.onopenmemory(U)),ut("click",Lt,()=>t.ondeletememory(U)),A(et,kt)});var K=T(H,2);ut("click",K,function(...et){var U;(U=t.oncreatememory)==null||U.apply(this,et)}),A(F,z)},$$slots:{default:!0}})};tt(P,O=>{t.data.available_memories&&O(E)})}var I=T(P,2),j=T(C(I),2);qt(j,{variant:"secondary",get onclick(){return t.oneditconfig},children:(O,F)=>{var V=yt("Edit Global Serena Config");A(O,V)},$$slots:{default:!0}}),G(()=>{q(o,t.data.serena_version),Pt(m,"title",t.data.context.path),q(p,t.data.context.name),q(S,t.data.encoding??"N/A")}),A(e,n),lt()}Me(["click"]);var Hd=R('
              '),Yd=R('
              '),Ud=R('
              No tool usage yet.
              ');function Xd(e,t){at(t,!0);const n=it(()=>Object.entries(t.stats).sort((l,c)=>c[1].num_calls-l[1].num_calls)),i=it(()=>Math.max(1,...b(n).map(([,l])=>l.num_calls)));var s=zt(),o=rt(s);{var r=l=>{var c=Yd();le(c,21,()=>b(n),([h,u])=>h,(h,u)=>{var f=it(()=>rc(b(u),2));let d=()=>b(f)[0],g=()=>b(f)[1];var m=Hd(),p=C(m),v=C(p),_=T(p,2),x=C(_),y=T(_,2),w=C(y);G(()=>{q(v,d()),Gc(x,`width:${g().num_calls/b(i)*100}%`),q(w,g().num_calls)}),A(h,m)}),A(l,c)},a=l=>{var c=Ud();A(l,c)};tt(o,l=>{b(n).length?l(r):l(a,-1)})}A(e,s),lt()}var qd=R('

              '),$d=R('
              ');function nn(e,t){let n=Xt(t,"title",3,"");var i=$d(),s=C(i);{var o=a=>{var l=qd(),c=C(l);G(()=>q(c,n())),A(a,l)};tt(s,a=>{n()&&a(o)})}var r=T(s,2);bs(r,()=>t.children),A(e,i)}var Kd=R("
            • "),Gd=R('
                '),Jd=R('
                None.
                ');function qo(e,t){at(t,!0),nn(e,{children:(n,i)=>{po(n,{get title(){return t.title},children:(s,o)=>{var r=zt(),a=rt(r);{var l=h=>{var u=Gd();le(u,21,()=>t.items,f=>f.name,(f,d)=>{var g=Kd();let m;var p=C(g);G(()=>{m=ue(g,1,"svelte-1mlfgti",null,m,{active:b(d).active}),q(p,b(d).name)}),A(f,g)}),A(h,u)},c=h=>{var u=Jd();A(h,u)};tt(a,h=>{t.items.length?h(l):h(c,-1)})}A(s,r)},$$slots:{default:!0}})},$$slots:{default:!0}}),lt()}var Zd=R('
              • '),Qd=R('
                  '),tg=R('
                  None.
                  ');function eg(e,t){at(t,!0),nn(e,{children:(n,i)=>{po(n,{get title(){return`Registered Projects (${t.projects.length??""})`},open:!0,children:(s,o)=>{var r=zt(),a=rt(r);{var l=h=>{var u=Qd();le(u,21,()=>t.projects,f=>f.path,(f,d)=>{var g=Zd();let m;var p=C(g),v=C(p),_=T(p,2),x=C(_);G(()=>{m=ue(g,1,"project svelte-19jixoo",null,m,{active:b(d).is_active}),q(v,b(d).name),q(x,b(d).path)}),A(f,g)}),A(h,u)},c=h=>{var u=tg();A(h,u)};tt(a,h=>{t.projects.length?h(l):h(c,-1)})}A(s,r)},$$slots:{default:!0}})},$$slots:{default:!0}}),lt()}Wu();var ng=R('
                  ');function xs(e){var t=ng();A(e,t)}var ig=R('
                  '),sg=R('
                  '),og=R('
                  No queued executions.
                  '),rg=R(''),ag=R(" ",1);function lg(e,t){at(t,!0);let n=Xt(t,"cancelError",3,"");const i=it(()=>t.items.filter(h=>h.logged));var s=ag(),o=rt(s);{var r=h=>{var u=sg();le(u,21,()=>b(i),f=>f.task_id,(f,d)=>{var g=ig();let m;var p=C(g);{var v=w=>{xs(w)};tt(p,w=>{b(d).is_running&&w(v)})}var _=T(p,2),x=C(_),y=T(_,2);G(()=>{m=ue(g,1,"execution-item svelte-1wrcimm",null,m,{running:b(d).is_running}),q(x,b(d).name),Pt(y,"aria-label",`Cancel ${b(d).name??""}`)}),ut("click",y,()=>t.oncancelexecution(b(d))),A(f,g)}),A(h,u)},a=h=>{var u=og();A(h,u)};tt(o,h=>{b(i).length?h(r):h(a,-1)})}var l=T(o,2);{var c=h=>{var u=rg(),f=C(u);G(()=>q(f,n())),A(h,u)};tt(l,h=>{n()&&h(c)})}A(e,s),lt()}Me(["click"]);var cg=R('
                  '),hg=R('
                  None yet.
                  ');function ug(e,t){at(t,!0);const n=it(()=>t.execution?t.execution.is_running?"Running":t.execution.finished_successfully?"Succeeded":"Failed":""),i=it(()=>t.execution?t.execution.is_running?"…":t.execution.finished_successfully?"✓":"✗":"");var s=zt(),o=rt(s);{var r=l=>{var c=cg();let h;var u=C(c),f=C(u),d=T(u,2),g=C(d),m=C(g),p=T(g,2),v=C(p),_=T(d,2),x=C(_);G(()=>{h=ue(c,1,"last-exec svelte-1772y66",null,h,{ok:t.execution.finished_successfully&&!t.execution.is_running,fail:!t.execution.finished_successfully&&!t.execution.is_running,running:t.execution.is_running}),q(f,b(i)),q(m,b(n)),q(v,t.execution.name),q(x,`#${t.execution.task_id??""}`)}),A(l,c)},a=l=>{var c=hg();A(l,c)};tt(o,l=>{t.execution?l(r):l(a,-1)})}A(e,s),lt()}var fg=R('
                  '),dg=R('
                  ');function gg(e,t){var n=dg();le(n,21,()=>t.items,i=>i.task_id,(i,s)=>{var o=fg();let r;var a=C(o),l=C(a),c=T(a,2),h=C(c),u=T(c,2),f=C(u);G(()=>{r=ue(o,1,"cancelled-item svelte-1re8sys",null,r,{abandoned:b(s).is_running}),q(l,b(s).is_running?"!":"✕"),q(h,b(s).name),q(f,`${b(s).is_running?"abandoned · ":""}#${b(s).task_id??""}`)}),A(i,o)}),A(e,n)}function pg(e){return Object.entries(e).sort((t,n)=>n[0].localeCompare(t[0]))}var mg=R('
                  '),vg=R(`

                  What's New

                  `);function _g(e,t){at(t,!0);let n=B(Tt([]));async function i(){L(n,pg((await kd()).news),!0)}Ze(()=>{i()});async function s(l){await Md(l),L(n,b(n).filter(([c])=>c!==l),!0)}var o=zt(),r=rt(o);{var a=l=>{var c=vg(),h=T(C(c),2);le(h,17,()=>b(n),([u,f])=>u,(u,f)=>{var d=it(()=>rc(b(f),2));let g=()=>b(d)[0],m=()=>b(d)[1];var p=mg(),v=C(p);Kc(v,m,!0);var _=T(v,2);ut("click",_,()=>s(g())),A(u,p)}),A(l,c)};tt(r,l=>{b(n).length&&l(a)})}A(e,o),lt()}Me(["click"]);var bg=R('
                  ');function xg(e,t){at(t,!0);const n=it(()=>go.data);var i=zt(),s=rt(i);{var o=a=>{xs(a)},r=a=>{var l=bg(),c=C(l),h=C(c);_g(h,{});var u=T(h,2);nn(u,{title:"Current Configuration",children:(M,P)=>{Wd(M,{get data(){return b(n)},get onaddlanguage(){return t.onaddlanguage},get onremovelanguage(){return t.onremovelanguage},get oneditconfig(){return t.oneditconfig},get onopenmemory(){return t.onopenmemory},get oncreatememory(){return t.oncreatememory},get ondeletememory(){return t.ondeletememory}})},$$slots:{default:!0}});var f=T(u,2);nn(f,{title:"Tool Usage",children:(M,P)=>{Xd(M,{get stats(){return b(n).tool_stats_summary}})},$$slots:{default:!0}});var d=T(f,2);nn(d,{title:"Executions Queue",children:(M,P)=>{lg(M,{get items(){return Oe.queued},get cancelError(){return Oe.cancelError},get oncancelexecution(){return t.oncancelexecution}})},$$slots:{default:!0}});var g=T(d,2);{var m=M=>{nn(M,{title:"Cancelled Executions",children:(P,E)=>{gg(P,{get items(){return Oe.cancelled}})},$$slots:{default:!0}})};tt(g,M=>{Oe.cancelled.length&&M(m)})}var p=T(g,2);nn(p,{title:"Last Execution",children:(M,P)=>{ug(M,{get execution(){return Oe.last}})},$$slots:{default:!0}});var v=T(c,2),_=C(v);eg(_,{get projects(){return b(n).registered_projects}});var x=T(_,2);{let M=it(()=>b(n).available_tools.filter(P=>!P.is_active).map(P=>({name:P.name})));qo(x,{title:"Available Tools (Disabled)",get items(){return b(M)}})}var y=T(x,2);{let M=it(()=>b(n).available_modes.map(P=>({name:P.name,active:P.is_active})));qo(y,{title:"Available Modes",get items(){return b(M)}})}var w=T(y,2);{let M=it(()=>b(n).available_contexts.map(P=>({name:P.name,active:P.is_active})));qo(w,{title:"Available Contexts",get items(){return b(M)}})}var S=T(w,2);nn(S,{children:(M,P)=>{Zc(M,{target:"gold"})},$$slots:{default:!0}}),A(a,l)};tt(s,a=>{b(n)?a(r,-1):a(o)})}A(e,i),lt()}var yg=R('
                  ');function wg(e,t){at(t,!0);let n=B(!1);const i=it(()=>t.lines.length===0);async function s(){try{await navigator.clipboard.writeText(t.lines.join(` -`)),L(n,!0),setTimeout(()=>L(n,!1),1e3)}catch{}}function o(){const u=new Blob([t.lines.join(` -`)],{type:"text/plain"}),f=URL.createObjectURL(u),d=document.createElement("a");d.href=f,d.download="serena-logs.txt",d.click(),setTimeout(()=>URL.revokeObjectURL(f),0)}var r=yg(),a=C(r),l=C(a),c=T(a,2),h=T(c,2);G(()=>{a.disabled=b(i),q(l,b(n)?"✓ copied":"copy logs"),c.disabled=b(i),h.disabled=b(i)}),ut("click",a,s),ut("click",c,o),ut("click",h,function(...u){var f;(f=t.onclear)==null||f.apply(this,u)}),A(e,r),lt()}Me(["click"]);function kg(e){return e.startsWith("DEBUG")?"debug":e.startsWith("INFO")?"info":e.startsWith("WARNING")?"warning":e.startsWith("ERROR")?"error":"info"}function Da(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function Mg(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Sg(e,t){const n=Da(e);if(t.length===0)return n;const i=[...t].sort((r,a)=>a.length-r.length),s=new Map(i.map(r=>[r.toLowerCase(),r])),o=new RegExp(`\\b(?:${i.map(Mg).join("|")})\\b`,"gi");return n.replace(o,r=>{const a=s.get(r.toLowerCase())??r;return`${Da(a)}`})}const Pg=new Intl.NumberFormat("en-US");function Cs(e){return Pg.format(e)}var Cg=R("
                  "),Ag=R('
                  ');function Dg(e,t){at(t,!0);let n=B(null),i=!1;Vn(()=>{if(t.lines.length,!b(n)||t.lines.length===0)return;if(!i){i=!0,queueMicrotask(()=>{b(n)&&(b(n).scrollTop=b(n).scrollHeight)});return}b(n).scrollHeight-b(n).scrollTop-b(n).clientHeight<40&&queueMicrotask(()=>{b(n)&&(b(n).scrollTop=b(n).scrollHeight)})});var s=Ag();le(s,21,()=>t.lines,Lf,(o,r)=>{var a=Cg();Kc(a,()=>Sg(b(r),t.toolNames),!0),G(l=>ue(a,1,`log-line ${l??""}`,"svelte-1f7p4v8"),[()=>kg(b(r))]),A(o,a)}),Oo(s,o=>L(n,o),()=>b(n)),A(e,s),lt()}var Tg=R(" ",1);function Og(e,t){at(t,!0);let n=B(Tt([]));Ze(()=>{(async()=>L(n,(await ad()).tool_names,!0))()});var i=Tg(),s=rt(i);wg(s,{get lines(){return eo.lines},onclear:()=>eo.clear()});var o=T(s,2);Dg(o,{get lines(){return eo.lines},get toolNames(){return b(n)}}),A(e,i),lt()}function Eg(){let e=B(Tt({})),t=B("unknown");return{get stats(){return b(e)},get estimator(){return b(t)},async refresh(){L(e,(await ld()).stats,!0),L(t,(await hd()).token_count_estimator_name,!0)},async clear(){await cd(),L(e,{},!0)}}}const Se=Eg();function th(e,t){return Object.entries(e).sort((n,i)=>i[1][t]-n[1][t])}const Lg=.04;function $o(e,t){const n=th(e,t);return{type:"pie",data:{labels:n.map(([i])=>i),datasets:[{data:n.map(([,i])=>i[t])}]},options:{plugins:{legend:{display:!0,position:"bottom",labels:{}},datalabels:{display:i=>{const s=i.dataset.data,o=s.reduce((r,a)=>r+a,0);return o===0?!1:s[i.dataIndex]/o>=Lg},color:"#ffffff",font:{weight:"bold"},formatter:i=>i}}}}}function Rg(e){const t=th(e,"input_tokens");return{type:"bar",data:{labels:t.map(([n])=>n),datasets:[{label:"Input Tokens",data:t.map(([,n])=>n.input_tokens),yAxisID:"y"},{label:"Output Tokens",data:t.map(([,n])=>n.output_tokens),yAxisID:"y1"}]},options:{responsive:!0,layout:{padding:{bottom:8}},plugins:{legend:{display:!0,labels:{}},datalabels:{display:!1}},scales:{x:{ticks:{maxRotation:35,padding:8}},y:{type:"linear",position:"left",beginAtZero:!0,title:{display:!0,text:"Input Tokens"}},y1:{type:"linear",position:"right",beginAtZero:!0,title:{display:!0,text:"Output Tokens"},grid:{drawOnChartArea:!1}}}}}}/*! - * @kurkle/color v0.3.4 - * https://github.com/kurkle/color#readme - * (c) 2024 Jukka Kurkela - * Released under the MIT License - */function ys(e){return e+.5|0}const cn=(e,t,n)=>Math.max(Math.min(e,n),t);function zi(e){return cn(ys(e*2.55),0,255)}function mn(e){return cn(ys(e*255),0,255)}function je(e){return cn(ys(e/2.55)/100,0,1)}function Ta(e){return cn(ys(e*100),0,100)}const ne={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Sr=[..."0123456789ABCDEF"],Ig=e=>Sr[e&15],Fg=e=>Sr[(e&240)>>4]+Sr[e&15],As=e=>(e&240)>>4===(e&15),zg=e=>As(e.r)&&As(e.g)&&As(e.b)&&As(e.a);function Ng(e){var t=e.length,n;return e[0]==="#"&&(t===4||t===5?n={r:255&ne[e[1]]*17,g:255&ne[e[2]]*17,b:255&ne[e[3]]*17,a:t===5?ne[e[4]]*17:255}:(t===7||t===9)&&(n={r:ne[e[1]]<<4|ne[e[2]],g:ne[e[3]]<<4|ne[e[4]],b:ne[e[5]]<<4|ne[e[6]],a:t===9?ne[e[7]]<<4|ne[e[8]]:255})),n}const Bg=(e,t)=>e<255?t(e):"";function jg(e){var t=zg(e)?Ig:Fg;return e?"#"+t(e.r)+t(e.g)+t(e.b)+Bg(e.a,t):void 0}const Vg=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function eh(e,t,n){const i=t*Math.min(n,1-n),s=(o,r=(o+e/30)%12)=>n-i*Math.max(Math.min(r-3,9-r,1),-1);return[s(0),s(8),s(4)]}function Wg(e,t,n){const i=(s,o=(s+e/60)%6)=>n-n*t*Math.max(Math.min(o,4-o,1),0);return[i(5),i(3),i(1)]}function Hg(e,t,n){const i=eh(e,1,.5);let s;for(t+n>1&&(s=1/(t+n),t*=s,n*=s),s=0;s<3;s++)i[s]*=1-t-n,i[s]+=t;return i}function Yg(e,t,n,i,s){return e===s?(t-n)/i+(t.5?h/(2-o-r):h/(o+r),l=Yg(n,i,s,h,o),l=l*60+.5),[l|0,c||0,a]}function na(e,t,n,i){return(Array.isArray(t)?e(t[0],t[1],t[2]):e(t,n,i)).map(mn)}function ia(e,t,n){return na(eh,e,t,n)}function Ug(e,t,n){return na(Hg,e,t,n)}function Xg(e,t,n){return na(Wg,e,t,n)}function nh(e){return(e%360+360)%360}function qg(e){const t=Vg.exec(e);let n=255,i;if(!t)return;t[5]!==i&&(n=t[6]?zi(+t[5]):mn(+t[5]));const s=nh(+t[2]),o=+t[3]/100,r=+t[4]/100;return t[1]==="hwb"?i=Ug(s,o,r):t[1]==="hsv"?i=Xg(s,o,r):i=ia(s,o,r),{r:i[0],g:i[1],b:i[2],a:n}}function $g(e,t){var n=ea(e);n[0]=nh(n[0]+t),n=ia(n),e.r=n[0],e.g=n[1],e.b=n[2]}function Kg(e){if(!e)return;const t=ea(e),n=t[0],i=Ta(t[1]),s=Ta(t[2]);return e.a<255?`hsla(${n}, ${i}%, ${s}%, ${je(e.a)})`:`hsl(${n}, ${i}%, ${s}%)`}const Oa={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},Ea={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};function Gg(){const e={},t=Object.keys(Ea),n=Object.keys(Oa);let i,s,o,r,a;for(i=0;i>16&255,o>>8&255,o&255]}return e}let Ds;function Jg(e){Ds||(Ds=Gg(),Ds.transparent=[0,0,0,0]);const t=Ds[e.toLowerCase()];return t&&{r:t[0],g:t[1],b:t[2],a:t.length===4?t[3]:255}}const Zg=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;function Qg(e){const t=Zg.exec(e);let n=255,i,s,o;if(t){if(t[7]!==i){const r=+t[7];n=t[8]?zi(r):cn(r*255,0,255)}return i=+t[1],s=+t[3],o=+t[5],i=255&(t[2]?zi(i):cn(i,0,255)),s=255&(t[4]?zi(s):cn(s,0,255)),o=255&(t[6]?zi(o):cn(o,0,255)),{r:i,g:s,b:o,a:n}}}function tp(e){return e&&(e.a<255?`rgba(${e.r}, ${e.g}, ${e.b}, ${je(e.a)})`:`rgb(${e.r}, ${e.g}, ${e.b})`)}const Ko=e=>e<=.0031308?e*12.92:Math.pow(e,1/2.4)*1.055-.055,ni=e=>e<=.04045?e/12.92:Math.pow((e+.055)/1.055,2.4);function ep(e,t,n){const i=ni(je(e.r)),s=ni(je(e.g)),o=ni(je(e.b));return{r:mn(Ko(i+n*(ni(je(t.r))-i))),g:mn(Ko(s+n*(ni(je(t.g))-s))),b:mn(Ko(o+n*(ni(je(t.b))-o))),a:e.a+n*(t.a-e.a)}}function Ts(e,t,n){if(e){let i=ea(e);i[t]=Math.max(0,Math.min(i[t]+i[t]*n,t===0?360:1)),i=ia(i),e.r=i[0],e.g=i[1],e.b=i[2]}}function ih(e,t){return e&&Object.assign(t||{},e)}function La(e){var t={r:0,g:0,b:0,a:255};return Array.isArray(e)?e.length>=3&&(t={r:e[0],g:e[1],b:e[2],a:255},e.length>3&&(t.a=mn(e[3]))):(t=ih(e,{r:0,g:0,b:0,a:1}),t.a=mn(t.a)),t}function np(e){return e.charAt(0)==="r"?Qg(e):qg(e)}class ns{constructor(t){if(t instanceof ns)return t;const n=typeof t;let i;n==="object"?i=La(t):n==="string"&&(i=Ng(t)||Jg(t)||np(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=ih(this._rgb);return t&&(t.a=je(t.a)),t}set rgb(t){this._rgb=La(t)}rgbString(){return this._valid?tp(this._rgb):void 0}hexString(){return this._valid?jg(this._rgb):void 0}hslString(){return this._valid?Kg(this._rgb):void 0}mix(t,n){if(t){const i=this.rgb,s=t.rgb;let o;const r=n===o?.5:n,a=2*r-1,l=i.a-s.a,c=((a*l===-1?a:(a+l)/(1+a*l))+1)/2;o=1-c,i.r=255&c*i.r+o*s.r+.5,i.g=255&c*i.g+o*s.g+.5,i.b=255&c*i.b+o*s.b+.5,i.a=r*i.a+(1-r)*s.a,this.rgb=i}return this}interpolate(t,n){return t&&(this._rgb=ep(this._rgb,t._rgb,n)),this}clone(){return new ns(this.rgb)}alpha(t){return this._rgb.a=mn(t),this}clearer(t){const n=this._rgb;return n.a*=1-t,this}greyscale(){const t=this._rgb,n=ys(t.r*.3+t.g*.59+t.b*.11);return t.r=t.g=t.b=n,this}opaquer(t){const n=this._rgb;return n.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return Ts(this._rgb,2,t),this}darken(t){return Ts(this._rgb,2,-t),this}saturate(t){return Ts(this._rgb,1,t),this}desaturate(t){return Ts(this._rgb,1,-t),this}rotate(t){return $g(this._rgb,t),this}}/*! - * Chart.js v4.5.1 - * https://www.chartjs.org - * (c) 2025 Chart.js Contributors - * Released under the MIT License - */function ze(){}const ip=(()=>{let e=0;return()=>e++})();function $(e){return e==null}function dt(e){if(Array.isArray&&Array.isArray(e))return!0;const t=Object.prototype.toString.call(e);return t.slice(0,7)==="[object"&&t.slice(-6)==="Array]"}function Z(e){return e!==null&&Object.prototype.toString.call(e)==="[object Object]"}function vt(e){return(typeof e=="number"||e instanceof Number)&&isFinite(+e)}function Gt(e,t){return vt(e)?e:t}function W(e,t){return typeof e>"u"?t:e}const sp=(e,t)=>typeof e=="string"&&e.endsWith("%")?parseFloat(e)/100:+e/t,sh=(e,t)=>typeof e=="string"&&e.endsWith("%")?parseFloat(e)/100*t:+e;function ht(e,t,n){if(e&&typeof e.call=="function")return e.apply(n,t)}function ct(e,t,n,i){let s,o,r;if(dt(e))for(o=e.length,s=0;se,x:e=>e.x,y:e=>e.y};function ap(e){const t=e.split("."),n=[];let i="";for(const s of t)i+=s,i.endsWith("\\")?i=i.slice(0,-1)+".":(n.push(i),i="");return n}function lp(e){const t=ap(e);return n=>{for(const i of t){if(i==="")break;n=n&&n[i]}return n}}function bn(e,t){return(Ra[t]||(Ra[t]=lp(t)))(e)}function sa(e){return e.charAt(0).toUpperCase()+e.slice(1)}const is=e=>typeof e<"u",xn=e=>typeof e=="function",Ia=(e,t)=>{if(e.size!==t.size)return!1;for(const n of e)if(!t.has(n))return!1;return!0};function cp(e){return e.type==="mouseup"||e.type==="click"||e.type==="contextmenu"}const st=Math.PI,gt=2*st,hp=gt+st,_o=Number.POSITIVE_INFINITY,up=st/180,xt=st/2,kn=st/4,Fa=st*2/3,hn=Math.log10,Le=Math.sign;function Xi(e,t,n){return Math.abs(e-t)s-o).pop(),t}function dp(e){return typeof e=="symbol"||typeof e=="object"&&e!==null&&!(Symbol.toPrimitive in e||"toString"in e||"valueOf"in e)}function xi(e){return!dp(e)&&!isNaN(parseFloat(e))&&isFinite(e)}function gp(e,t){const n=Math.round(e);return n-t<=e&&n+t>=e}function rh(e,t,n){let i,s,o;for(i=0,s=e.length;il&&c=Math.min(t,n)-i&&e<=Math.max(t,n)+i}function ra(e,t,n){n=n||(r=>e[r]1;)o=s+i>>1,n(o)?s=o:i=o;return{lo:s,hi:i}}const Xe=(e,t,n,i)=>ra(e,n,i?s=>{const o=e[s][t];return oe[s][t]ra(e,n,i=>e[i][t]>=n);function _p(e,t,n){let i=0,s=e.length;for(;ii&&e[s-1]>n;)s--;return i>0||s{const i="_onData"+sa(n),s=e[n];Object.defineProperty(e,n,{configurable:!0,enumerable:!1,value(...o){const r=s.apply(this,o);return e._chartjs.listeners.forEach(a=>{typeof a[i]=="function"&&a[i](...o)}),r}})})}function Ba(e,t){const n=e._chartjs;if(!n)return;const i=n.listeners,s=i.indexOf(t);s!==-1&&i.splice(s,1),!(i.length>0)&&(lh.forEach(o=>{delete e[o]}),delete e._chartjs)}function ch(e){const t=new Set(e);return t.size===e.length?e:Array.from(t)}const hh=(function(){return typeof window>"u"?function(e){return e()}:window.requestAnimationFrame})();function uh(e,t){let n=[],i=!1;return function(...s){n=s,i||(i=!0,hh.call(window,()=>{i=!1,e.apply(t,n)}))}}function xp(e,t){let n;return function(...i){return t?(clearTimeout(n),n=setTimeout(e,t,i)):e.apply(this,i),t}}const aa=e=>e==="start"?"left":e==="end"?"right":"center",Rt=(e,t,n)=>e==="start"?t:e==="end"?n:(t+n)/2,yp=(e,t,n,i)=>e===(i?"left":"right")?n:e==="center"?(t+n)/2:t;function fh(e,t,n){const i=t.length;let s=0,o=i;if(e._sorted){const{iScale:r,vScale:a,_parsed:l}=e,c=e.dataset&&e.dataset.options?e.dataset.options.spanGaps:null,h=r.axis,{min:u,max:f,minDefined:d,maxDefined:g}=r.getUserBounds();if(d){if(s=Math.min(Xe(l,h,u).lo,n?i:Xe(t,h,r.getPixelForValue(u)).lo),c){const m=l.slice(0,s+1).reverse().findIndex(p=>!$(p[a.axis]));s-=Math.max(0,m)}s=At(s,0,i-1)}if(g){let m=Math.max(Xe(l,r.axis,f,!0).hi+1,n?0:Xe(t,h,r.getPixelForValue(f),!0).hi+1);if(c){const p=l.slice(m-1).findIndex(v=>!$(v[a.axis]));m+=Math.max(0,p)}o=At(m,s,i)-s}else o=i-s}return{start:s,count:o}}function dh(e){const{xScale:t,yScale:n,_scaleRanges:i}=e,s={xmin:t.min,xmax:t.max,ymin:n.min,ymax:n.max};if(!i)return e._scaleRanges=s,!0;const o=i.xmin!==t.min||i.xmax!==t.max||i.ymin!==n.min||i.ymax!==n.max;return Object.assign(i,s),o}const Os=e=>e===0||e===1,ja=(e,t,n)=>-(Math.pow(2,10*(e-=1))*Math.sin((e-t)*gt/n)),Va=(e,t,n)=>Math.pow(2,-10*e)*Math.sin((e-t)*gt/n)+1,qi={linear:e=>e,easeInQuad:e=>e*e,easeOutQuad:e=>-e*(e-2),easeInOutQuad:e=>(e/=.5)<1?.5*e*e:-.5*(--e*(e-2)-1),easeInCubic:e=>e*e*e,easeOutCubic:e=>(e-=1)*e*e+1,easeInOutCubic:e=>(e/=.5)<1?.5*e*e*e:.5*((e-=2)*e*e+2),easeInQuart:e=>e*e*e*e,easeOutQuart:e=>-((e-=1)*e*e*e-1),easeInOutQuart:e=>(e/=.5)<1?.5*e*e*e*e:-.5*((e-=2)*e*e*e-2),easeInQuint:e=>e*e*e*e*e,easeOutQuint:e=>(e-=1)*e*e*e*e+1,easeInOutQuint:e=>(e/=.5)<1?.5*e*e*e*e*e:.5*((e-=2)*e*e*e*e+2),easeInSine:e=>-Math.cos(e*xt)+1,easeOutSine:e=>Math.sin(e*xt),easeInOutSine:e=>-.5*(Math.cos(st*e)-1),easeInExpo:e=>e===0?0:Math.pow(2,10*(e-1)),easeOutExpo:e=>e===1?1:-Math.pow(2,-10*e)+1,easeInOutExpo:e=>Os(e)?e:e<.5?.5*Math.pow(2,10*(e*2-1)):.5*(-Math.pow(2,-10*(e*2-1))+2),easeInCirc:e=>e>=1?e:-(Math.sqrt(1-e*e)-1),easeOutCirc:e=>Math.sqrt(1-(e-=1)*e),easeInOutCirc:e=>(e/=.5)<1?-.5*(Math.sqrt(1-e*e)-1):.5*(Math.sqrt(1-(e-=2)*e)+1),easeInElastic:e=>Os(e)?e:ja(e,.075,.3),easeOutElastic:e=>Os(e)?e:Va(e,.075,.3),easeInOutElastic(e){return Os(e)?e:e<.5?.5*ja(e*2,.1125,.45):.5+.5*Va(e*2-1,.1125,.45)},easeInBack(e){return e*e*((1.70158+1)*e-1.70158)},easeOutBack(e){return(e-=1)*e*((1.70158+1)*e+1.70158)+1},easeInOutBack(e){let t=1.70158;return(e/=.5)<1?.5*(e*e*(((t*=1.525)+1)*e-t)):.5*((e-=2)*e*(((t*=1.525)+1)*e+t)+2)},easeInBounce:e=>1-qi.easeOutBounce(1-e),easeOutBounce(e){return e<1/2.75?7.5625*e*e:e<2/2.75?7.5625*(e-=1.5/2.75)*e+.75:e<2.5/2.75?7.5625*(e-=2.25/2.75)*e+.9375:7.5625*(e-=2.625/2.75)*e+.984375},easeInOutBounce:e=>e<.5?qi.easeInBounce(e*2)*.5:qi.easeOutBounce(e*2-1)*.5+.5};function la(e){if(e&&typeof e=="object"){const t=e.toString();return t==="[object CanvasPattern]"||t==="[object CanvasGradient]"}return!1}function Wa(e){return la(e)?e:new ns(e)}function Go(e){return la(e)?e:new ns(e).saturate(.5).darken(.1).hexString()}const wp=["x","y","borderWidth","radius","tension"],kp=["color","borderColor","backgroundColor"];function Mp(e){e.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),e.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:t=>t!=="onProgress"&&t!=="onComplete"&&t!=="fn"}),e.set("animations",{colors:{type:"color",properties:kp},numbers:{type:"number",properties:wp}}),e.describe("animations",{_fallback:"animation"}),e.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>t|0}}}})}function Sp(e){e.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})}const Ha=new Map;function Pp(e,t){t=t||{};const n=e+JSON.stringify(t);let i=Ha.get(n);return i||(i=new Intl.NumberFormat(e,t),Ha.set(n,i)),i}function ws(e,t,n){return Pp(t,n).format(e)}const gh={values(e){return dt(e)?e:""+e},numeric(e,t,n){if(e===0)return"0";const i=this.chart.options.locale;let s,o=e;if(n.length>1){const c=Math.max(Math.abs(n[0].value),Math.abs(n[n.length-1].value));(c<1e-4||c>1e15)&&(s="scientific"),o=Cp(e,n)}const r=hn(Math.abs(o)),a=isNaN(r)?1:Math.max(Math.min(-1*Math.floor(r),20),0),l={notation:s,minimumFractionDigits:a,maximumFractionDigits:a};return Object.assign(l,this.options.ticks.format),ws(e,i,l)},logarithmic(e,t,n){if(e===0)return"0";const i=n[t].significand||e/Math.pow(10,Math.floor(hn(e)));return[1,2,3,5,10,15].includes(i)||t>.8*n.length?gh.numeric.call(this,e,t,n):""}};function Cp(e,t){let n=t.length>3?t[2].value-t[1].value:t[1].value-t[0].value;return Math.abs(n)>=1&&e!==Math.floor(e)&&(n=e-Math.floor(e)),n}var Eo={formatters:gh};function Ap(e){e.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",clip:!0,grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(t,n)=>n.lineWidth,tickColor:(t,n)=>n.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:Eo.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),e.route("scale.ticks","color","","color"),e.route("scale.grid","color","","borderColor"),e.route("scale.border","color","","borderColor"),e.route("scale.title","color","","color"),e.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&t!=="callback"&&t!=="parser",_indexable:t=>t!=="borderDash"&&t!=="tickBorderDash"&&t!=="dash"}),e.describe("scales",{_fallback:"scale"}),e.describe("scale.ticks",{_scriptable:t=>t!=="backdropPadding"&&t!=="callback",_indexable:t=>t!=="backdropPadding"})}const Jn=Object.create(null),Cr=Object.create(null);function $i(e,t){if(!t)return e;const n=t.split(".");for(let i=0,s=n.length;ii.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(i,s)=>Go(s.backgroundColor),this.hoverBorderColor=(i,s)=>Go(s.borderColor),this.hoverColor=(i,s)=>Go(s.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t),this.apply(n)}set(t,n){return Jo(this,t,n)}get(t){return $i(this,t)}describe(t,n){return Jo(Cr,t,n)}override(t,n){return Jo(Jn,t,n)}route(t,n,i,s){const o=$i(this,t),r=$i(this,i),a="_"+n;Object.defineProperties(o,{[a]:{value:o[n],writable:!0},[n]:{enumerable:!0,get(){const l=this[a],c=r[s];return Z(l)?Object.assign({},c,l):W(l,c)},set(l){this[a]=l}}})}apply(t){t.forEach(n=>n(this))}}var pt=new Dp({_scriptable:e=>!e.startsWith("on"),_indexable:e=>e!=="events",hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[Mp,Sp,Ap]);function Tp(e){return!e||$(e.size)||$(e.family)?null:(e.style?e.style+" ":"")+(e.weight?e.weight+" ":"")+e.size+"px "+e.family}function bo(e,t,n,i,s){let o=t[s];return o||(o=t[s]=e.measureText(s).width,n.push(s)),o>i&&(i=o),i}function Op(e,t,n,i){i=i||{};let s=i.data=i.data||{},o=i.garbageCollect=i.garbageCollect||[];i.font!==t&&(s=i.data={},o=i.garbageCollect=[],i.font=t),e.save(),e.font=t;let r=0;const a=n.length;let l,c,h,u,f;for(l=0;ln.length){for(l=0;l0&&e.stroke()}}function qe(e,t,n){return n=n||.5,!t||e&&e.x>t.left-n&&e.xt.top-n&&e.y0&&o.strokeColor!=="";let l,c;for(e.save(),e.font=s.string,Rp(e,o),l=0;l+e||0;function ca(e,t){const n={},i=Z(t),s=i?Object.keys(t):t,o=Z(e)?i?r=>W(e[r],e[t[r]]):r=>e[r]:()=>e;for(const r of s)n[r]=jp(o(r));return n}function mh(e){return ca(e,{top:"y",right:"x",bottom:"y",left:"x"})}function Un(e){return ca(e,["topLeft","topRight","bottomLeft","bottomRight"])}function Et(e){const t=mh(e);return t.width=t.left+t.right,t.height=t.top+t.bottom,t}function wt(e,t){e=e||{},t=t||pt.font;let n=W(e.size,t.size);typeof n=="string"&&(n=parseInt(n,10));let i=W(e.style,t.style);i&&!(""+i).match(Np)&&(console.warn('Invalid font style specified: "'+i+'"'),i=void 0);const s={family:W(e.family,t.family),lineHeight:Bp(W(e.lineHeight,t.lineHeight),n),size:n,style:i,weight:W(e.weight,t.weight),string:""};return s.string=Tp(s),s}function ft(e,t,n,i){let s,o,r;for(s=0,o=e.length;sn&&a===0?0:a+l;return{min:r(i,-Math.abs(o)),max:r(s,o)}}function yn(e,t){return Object.assign(Object.create(e),t)}function ha(e,t=[""],n,i,s=()=>e[0]){const o=n||e;typeof i>"u"&&(i=xh("_fallback",e));const r={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:e,_rootScopes:o,_fallback:i,_getTarget:s,override:a=>ha([a,...e],t,o,i)};return new Proxy(r,{deleteProperty(a,l){return delete a[l],delete a._keys,delete e[0][l],!0},get(a,l){return _h(a,l,()=>Kp(l,t,e,a))},getOwnPropertyDescriptor(a,l){return Reflect.getOwnPropertyDescriptor(a._scopes[0],l)},getPrototypeOf(){return Reflect.getPrototypeOf(e[0])},has(a,l){return Xa(a).includes(l)},ownKeys(a){return Xa(a)},set(a,l,c){const h=a._storage||(a._storage=s());return a[l]=h[l]=c,delete a._keys,!0}})}function yi(e,t,n,i){const s={_cacheable:!1,_proxy:e,_context:t,_subProxy:n,_stack:new Set,_descriptors:vh(e,i),setContext:o=>yi(e,o,n,i),override:o=>yi(e.override(o),t,n,i)};return new Proxy(s,{deleteProperty(o,r){return delete o[r],delete e[r],!0},get(o,r,a){return _h(o,r,()=>Hp(o,r,a))},getOwnPropertyDescriptor(o,r){return o._descriptors.allKeys?Reflect.has(e,r)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(e,r)},getPrototypeOf(){return Reflect.getPrototypeOf(e)},has(o,r){return Reflect.has(e,r)},ownKeys(){return Reflect.ownKeys(e)},set(o,r,a){return e[r]=a,delete o[r],!0}})}function vh(e,t={scriptable:!0,indexable:!0}){const{_scriptable:n=t.scriptable,_indexable:i=t.indexable,_allKeys:s=t.allKeys}=e;return{allKeys:s,scriptable:n,indexable:i,isScriptable:xn(n)?n:()=>n,isIndexable:xn(i)?i:()=>i}}const Wp=(e,t)=>e?e+sa(t):t,ua=(e,t)=>Z(t)&&e!=="adapters"&&(Object.getPrototypeOf(t)===null||t.constructor===Object);function _h(e,t,n){if(Object.prototype.hasOwnProperty.call(e,t)||t==="constructor")return e[t];const i=n();return e[t]=i,i}function Hp(e,t,n){const{_proxy:i,_context:s,_subProxy:o,_descriptors:r}=e;let a=i[t];return xn(a)&&r.isScriptable(t)&&(a=Yp(t,a,e,n)),dt(a)&&a.length&&(a=Up(t,a,e,r.isIndexable)),ua(t,a)&&(a=yi(a,s,o&&o[t],r)),a}function Yp(e,t,n,i){const{_proxy:s,_context:o,_subProxy:r,_stack:a}=n;if(a.has(e))throw new Error("Recursion detected: "+Array.from(a).join("->")+"->"+e);a.add(e);let l=t(o,r||i);return a.delete(e),ua(e,l)&&(l=fa(s._scopes,s,e,l)),l}function Up(e,t,n,i){const{_proxy:s,_context:o,_subProxy:r,_descriptors:a}=n;if(typeof o.index<"u"&&i(e))return t[o.index%t.length];if(Z(t[0])){const l=t,c=s._scopes.filter(h=>h!==l);t=[];for(const h of l){const u=fa(c,s,e,h);t.push(yi(u,o,r&&r[e],a))}}return t}function bh(e,t,n){return xn(e)?e(t,n):e}const Xp=(e,t)=>e===!0?t:typeof e=="string"?bn(t,e):void 0;function qp(e,t,n,i,s){for(const o of t){const r=Xp(n,o);if(r){e.add(r);const a=bh(r._fallback,n,s);if(typeof a<"u"&&a!==n&&a!==i)return a}else if(r===!1&&typeof i<"u"&&n!==i)return null}return!1}function fa(e,t,n,i){const s=t._rootScopes,o=bh(t._fallback,n,i),r=[...e,...s],a=new Set;a.add(i);let l=Ua(a,r,n,o||n,i);return l===null||typeof o<"u"&&o!==n&&(l=Ua(a,r,o,l,i),l===null)?!1:ha(Array.from(a),[""],s,o,()=>$p(t,n,i))}function Ua(e,t,n,i,s){for(;n;)n=qp(e,t,n,i,s);return n}function $p(e,t,n){const i=e._getTarget();t in i||(i[t]={});const s=i[t];return dt(s)&&Z(n)?n:s||{}}function Kp(e,t,n,i){let s;for(const o of t)if(s=xh(Wp(o,e),n),typeof s<"u")return ua(e,s)?fa(n,i,e,s):s}function xh(e,t){for(const n of t){if(!n)continue;const i=n[e];if(typeof i<"u")return i}}function Xa(e){let t=e._keys;return t||(t=e._keys=Gp(e._scopes)),t}function Gp(e){const t=new Set;for(const n of e)for(const i of Object.keys(n).filter(s=>!s.startsWith("_")))t.add(i);return Array.from(t)}function yh(e,t,n,i){const{iScale:s}=e,{key:o="r"}=this._parsing,r=new Array(i);let a,l,c,h;for(a=0,l=i;ate==="x"?"y":"x";function Zp(e,t,n,i){const s=e.skip?t:e,o=t,r=n.skip?t:n,a=Pr(o,s),l=Pr(r,o);let c=a/(a+l),h=l/(a+l);c=isNaN(c)?0:c,h=isNaN(h)?0:h;const u=i*c,f=i*h;return{previous:{x:o.x-u*(r.x-s.x),y:o.y-u*(r.y-s.y)},next:{x:o.x+f*(r.x-s.x),y:o.y+f*(r.y-s.y)}}}function Qp(e,t,n){const i=e.length;let s,o,r,a,l,c=wi(e,0);for(let h=0;h!c.skip)),t.cubicInterpolationMode==="monotone")em(e,s);else{let c=i?e[e.length-1]:e[0];for(o=0,r=e.length;oe.ownerDocument.defaultView.getComputedStyle(e,null);function sm(e,t){return Io(e).getPropertyValue(t)}const om=["top","right","bottom","left"];function Xn(e,t,n){const i={};n=n?"-"+n:"";for(let s=0;s<4;s++){const o=om[s];i[o]=parseFloat(e[t+"-"+o+n])||0}return i.width=i.left+i.right,i.height=i.top+i.bottom,i}const rm=(e,t,n)=>(e>0||t>0)&&(!n||!n.shadowRoot);function am(e,t){const n=e.touches,i=n&&n.length?n[0]:e,{offsetX:s,offsetY:o}=i;let r=!1,a,l;if(rm(s,o,e.target))a=s,l=o;else{const c=t.getBoundingClientRect();a=i.clientX-c.left,l=i.clientY-c.top,r=!0}return{x:a,y:l,box:r}}function Cn(e,t){if("native"in e)return e;const{canvas:n,currentDevicePixelRatio:i}=t,s=Io(n),o=s.boxSizing==="border-box",r=Xn(s,"padding"),a=Xn(s,"border","width"),{x:l,y:c,box:h}=am(e,n),u=r.left+(h&&a.left),f=r.top+(h&&a.top);let{width:d,height:g}=t;return o&&(d-=r.width+a.width,g-=r.height+a.height),{x:Math.round((l-u)/d*n.width/i),y:Math.round((c-f)/g*n.height/i)}}function lm(e,t,n){let i,s;if(t===void 0||n===void 0){const o=e&&ga(e);if(!o)t=e.clientWidth,n=e.clientHeight;else{const r=o.getBoundingClientRect(),a=Io(o),l=Xn(a,"border","width"),c=Xn(a,"padding");t=r.width-c.width-l.width,n=r.height-c.height-l.height,i=xo(a.maxWidth,o,"clientWidth"),s=xo(a.maxHeight,o,"clientHeight")}}return{width:t,height:n,maxWidth:i||_o,maxHeight:s||_o}}const un=e=>Math.round(e*10)/10;function cm(e,t,n,i){const s=Io(e),o=Xn(s,"margin"),r=xo(s.maxWidth,e,"clientWidth")||_o,a=xo(s.maxHeight,e,"clientHeight")||_o,l=lm(e,t,n);let{width:c,height:h}=l;if(s.boxSizing==="content-box"){const f=Xn(s,"border","width"),d=Xn(s,"padding");c-=d.width+f.width,h-=d.height+f.height}return c=Math.max(0,c-o.width),h=Math.max(0,i?c/i:h-o.height),c=un(Math.min(c,r,l.maxWidth)),h=un(Math.min(h,a,l.maxHeight)),c&&!h&&(h=un(c/2)),(t!==void 0||n!==void 0)&&i&&l.height&&h>l.height&&(h=l.height,c=un(Math.floor(h*i))),{width:c,height:h}}function qa(e,t,n){const i=t||1,s=un(e.height*i),o=un(e.width*i);e.height=un(e.height),e.width=un(e.width);const r=e.canvas;return r.style&&(n||!r.style.height&&!r.style.width)&&(r.style.height=`${e.height}px`,r.style.width=`${e.width}px`),e.currentDevicePixelRatio!==i||r.height!==s||r.width!==o?(e.currentDevicePixelRatio=i,r.height=s,r.width=o,e.ctx.setTransform(i,0,0,i,0,0),!0):!1}const hm=(function(){let e=!1;try{const t={get passive(){return e=!0,!1}};da()&&(window.addEventListener("test",null,t),window.removeEventListener("test",null,t))}catch{}return e})();function $a(e,t){const n=sm(e,t),i=n&&n.match(/^(\d+)(\.\d+)?px$/);return i?+i[1]:void 0}function An(e,t,n,i){return{x:e.x+n*(t.x-e.x),y:e.y+n*(t.y-e.y)}}function um(e,t,n,i){return{x:e.x+n*(t.x-e.x),y:i==="middle"?n<.5?e.y:t.y:i==="after"?n<1?e.y:t.y:n>0?t.y:e.y}}function fm(e,t,n,i){const s={x:e.cp2x,y:e.cp2y},o={x:t.cp1x,y:t.cp1y},r=An(e,s,n),a=An(s,o,n),l=An(o,t,n),c=An(r,a,n),h=An(a,l,n);return An(c,h,n)}const dm=function(e,t){return{x(n){return e+e+t-n},setWidth(n){t=n},textAlign(n){return n==="center"?n:n==="right"?"left":"right"},xPlus(n,i){return n-i},leftForLtr(n,i){return n-i}}},gm=function(){return{x(e){return e},setWidth(e){},textAlign(e){return e},xPlus(e,t){return e+t},leftForLtr(e,t){return e}}};function li(e,t,n){return e?dm(t,n):gm()}function kh(e,t){let n,i;(t==="ltr"||t==="rtl")&&(n=e.canvas.style,i=[n.getPropertyValue("direction"),n.getPropertyPriority("direction")],n.setProperty("direction",t,"important"),e.prevTextDirection=i)}function Mh(e,t){t!==void 0&&(delete e.prevTextDirection,e.canvas.style.setProperty("direction",t[0],t[1]))}function Sh(e){return e==="angle"?{between:ss,compare:pp,normalize:It}:{between:Ue,compare:(t,n)=>t-n,normalize:t=>t}}function Ka({start:e,end:t,count:n,loop:i,style:s}){return{start:e%n,end:t%n,loop:i&&(t-e+1)%n===0,style:s}}function pm(e,t,n){const{property:i,start:s,end:o}=n,{between:r,normalize:a}=Sh(i),l=t.length;let{start:c,end:h,loop:u}=e,f,d;if(u){for(c+=l,h+=l,f=0,d=l;fl(s,x,v)&&a(s,x)!==0,w=()=>a(o,v)===0||l(o,x,v),S=()=>m||y(),M=()=>!m||w();for(let P=h,E=h;P<=u;++P)_=t[P%r],!_.skip&&(v=c(_[i]),v!==x&&(m=l(v,s,o),p===null&&S()&&(p=a(v,s)===0?P:E),p!==null&&M()&&(g.push(Ka({start:p,end:P,loop:f,count:r,style:d})),p=null),E=P,x=v));return p!==null&&g.push(Ka({start:p,end:u,loop:f,count:r,style:d})),g}function Ch(e,t){const n=[],i=e.segments;for(let s=0;ss&&e[o%t].skip;)o--;return o%=t,{start:s,end:o}}function vm(e,t,n,i){const s=e.length,o=[];let r=t,a=e[t],l;for(l=t+1;l<=n;++l){const c=e[l%s];c.skip||c.stop?a.skip||(i=!1,o.push({start:t%s,end:(l-1)%s,loop:i}),t=r=c.stop?l:null):(r=l,a.skip&&(t=l)),a=c}return r!==null&&o.push({start:t%s,end:r%s,loop:i}),o}function _m(e,t){const n=e.points,i=e.options.spanGaps,s=n.length;if(!s)return[];const o=!!e._loop,{start:r,end:a}=mm(n,s,o,i);if(i===!0)return Ga(e,[{start:r,end:a,loop:o}],n,t);const l=aa({chart:t,initial:n.initial,numSteps:r,currentStep:Math.min(i-n.start,r)}))}_refresh(){this._request||(this._running=!0,this._request=hh.call(window,()=>{this._update(),this._request=null,this._running&&this._refresh()}))}_update(t=Date.now()){let n=0;this._charts.forEach((i,s)=>{if(!i.running||!i.items.length)return;const o=i.items;let r=o.length-1,a=!1,l;for(;r>=0;--r)l=o[r],l._active?(l._total>i.duration&&(i.duration=l._total),l.tick(t),a=!0):(o[r]=o[o.length-1],o.pop());a&&(s.draw(),this._notify(s,i,t,"progress")),o.length||(i.running=!1,this._notify(s,i,t,"complete"),i.initial=!1),n+=o.length}),this._lastDate=t,n===0&&(this._running=!1)}_getAnims(t){const n=this._charts;let i=n.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},n.set(t,i)),i}listen(t,n,i){this._getAnims(t).listeners[n].push(i)}add(t,n){!n||!n.length||this._getAnims(t).items.push(...n)}has(t){return this._getAnims(t).items.length>0}start(t){const n=this._charts.get(t);n&&(n.running=!0,n.start=Date.now(),n.duration=n.items.reduce((i,s)=>Math.max(i,s._duration),0),this._refresh())}running(t){if(!this._running)return!1;const n=this._charts.get(t);return!(!n||!n.running||!n.items.length)}stop(t){const n=this._charts.get(t);if(!n||!n.items.length)return;const i=n.items;let s=i.length-1;for(;s>=0;--s)i[s].cancel();n.items=[],this._notify(t,n,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}var Ne=new wm;const Za="transparent",km={boolean(e,t,n){return n>.5?t:e},color(e,t,n){const i=Wa(e||Za),s=i.valid&&Wa(t||Za);return s&&s.valid?s.mix(i,n).hexString():t},number(e,t,n){return e+(t-e)*n}};class Mm{constructor(t,n,i,s){const o=n[i];s=ft([t.to,s,o,t.from]);const r=ft([t.from,o,s]);this._active=!0,this._fn=t.fn||km[t.type||typeof r],this._easing=qi[t.easing]||qi.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=n,this._prop=i,this._from=r,this._to=s,this._promises=void 0}active(){return this._active}update(t,n,i){if(this._active){this._notify(!1);const s=this._target[this._prop],o=i-this._start,r=this._duration-o;this._start=i,this._duration=Math.floor(Math.max(r,t.duration)),this._total+=o,this._loop=!!t.loop,this._to=ft([t.to,n,s,t.from]),this._from=ft([t.from,s,n])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){const n=t-this._start,i=this._duration,s=this._prop,o=this._from,r=this._loop,a=this._to;let l;if(this._active=o!==a&&(r||n1?2-l:l,l=this._easing(Math.min(1,Math.max(0,l))),this._target[s]=this._fn(o,a,l)}wait(){const t=this._promises||(this._promises=[]);return new Promise((n,i)=>{t.push({res:n,rej:i})})}_notify(t){const n=t?"res":"rej",i=this._promises||[];for(let s=0;s{const o=t[s];if(!Z(o))return;const r={};for(const a of n)r[a]=o[a];(dt(o.properties)&&o.properties||[s]).forEach(a=>{(a===s||!i.has(a))&&i.set(a,r)})})}_animateOptions(t,n){const i=n.options,s=Pm(t,i);if(!s)return[];const o=this._createAnimations(s,i);return i.$shared&&Sm(t.options.$animations,i).then(()=>{t.options=i},()=>{}),o}_createAnimations(t,n){const i=this._properties,s=[],o=t.$animations||(t.$animations={}),r=Object.keys(n),a=Date.now();let l;for(l=r.length-1;l>=0;--l){const c=r[l];if(c.charAt(0)==="$")continue;if(c==="options"){s.push(...this._animateOptions(t,n));continue}const h=n[c];let u=o[c];const f=i.get(c);if(u)if(f&&u.active()){u.update(f,h,a);continue}else u.cancel();if(!f||!f.duration){t[c]=h;continue}o[c]=u=new Mm(f,t,c,h),s.push(u)}return s}update(t,n){if(this._properties.size===0){Object.assign(t,n);return}const i=this._createAnimations(t,n);if(i.length)return Ne.add(this._chart,i),!0}}function Sm(e,t){const n=[],i=Object.keys(t);for(let s=0;s0||!n&&o<0)return s.index}return null}function nl(e,t){const{chart:n,_cachedMeta:i}=e,s=n._stacks||(n._stacks={}),{iScale:o,vScale:r,index:a}=i,l=o.axis,c=r.axis,h=Tm(o,r,i),u=t.length;let f;for(let d=0;dn[i].axis===t).shift()}function Lm(e,t){return yn(e,{active:!1,dataset:void 0,datasetIndex:t,index:t,mode:"default",type:"dataset"})}function Rm(e,t,n){return yn(e,{active:!1,dataIndex:t,parsed:void 0,raw:void 0,element:n,index:t,mode:"default",type:"data"})}function Ai(e,t){const n=e.controller.index,i=e.vScale&&e.vScale.axis;if(i){t=t||e._parsed;for(const s of t){const o=s._stacks;if(!o||o[i]===void 0||o[i][n]===void 0)return;delete o[i][n],o[i]._visualValues!==void 0&&o[i]._visualValues[n]!==void 0&&delete o[i]._visualValues[n]}}}const tr=e=>e==="reset"||e==="none",il=(e,t)=>t?e:Object.assign({},e),Im=(e,t,n)=>e&&!t.hidden&&t._stacked&&{keys:Th(n,!0),values:null};class we{constructor(t,n){this.chart=t,this._ctx=t.ctx,this.index=n,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=Zo(t.vScale,t),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(t){this.index!==t&&Ai(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,n=this._cachedMeta,i=this.getDataset(),s=(u,f,d,g)=>u==="x"?f:u==="r"?g:d,o=n.xAxisID=W(i.xAxisID,Qo(t,"x")),r=n.yAxisID=W(i.yAxisID,Qo(t,"y")),a=n.rAxisID=W(i.rAxisID,Qo(t,"r")),l=n.indexAxis,c=n.iAxisID=s(l,o,r,a),h=n.vAxisID=s(l,r,o,a);n.xScale=this.getScaleForId(o),n.yScale=this.getScaleForId(r),n.rScale=this.getScaleForId(a),n.iScale=this.getScaleForId(c),n.vScale=this.getScaleForId(h)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const n=this._cachedMeta;return t===n.iScale?n.vScale:n.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&Ba(this._data,this),t._stacked&&Ai(t)}_dataCheck(){const t=this.getDataset(),n=t.data||(t.data=[]),i=this._data;if(Z(n)){const s=this._cachedMeta;this._data=Dm(n,s)}else if(i!==n){if(i){Ba(i,this);const s=this._cachedMeta;Ai(s),s._parsed=[]}n&&Object.isExtensible(n)&&bp(n,this),this._syncList=[],this._data=n}}addElements(){const t=this._cachedMeta;this._dataCheck(),this.datasetElementType&&(t.dataset=new this.datasetElementType)}buildOrUpdateElements(t){const n=this._cachedMeta,i=this.getDataset();let s=!1;this._dataCheck();const o=n._stacked;n._stacked=Zo(n.vScale,n),n.stack!==i.stack&&(s=!0,Ai(n),n.stack=i.stack),this._resyncElements(t),(s||o!==n._stacked)&&(nl(this,n._parsed),n._stacked=Zo(n.vScale,n))}configure(){const t=this.chart.config,n=t.datasetScopeKeys(this._type),i=t.getOptionScopes(this.getDataset(),n,!0);this.options=t.createResolver(i,this.getContext()),this._parsing=this.options.parsing,this._cachedDataOpts={}}parse(t,n){const{_cachedMeta:i,_data:s}=this,{iScale:o,_stacked:r}=i,a=o.axis;let l=t===0&&n===s.length?!0:i._sorted,c=t>0&&i._parsed[t-1],h,u,f;if(this._parsing===!1)i._parsed=s,i._sorted=!0,f=s;else{dt(s[t])?f=this.parseArrayData(i,s,t,n):Z(s[t])?f=this.parseObjectData(i,s,t,n):f=this.parsePrimitiveData(i,s,t,n);const d=()=>u[a]===null||c&&u[a]m||u=0;--f)if(!g()){this.updateRangeFromParsed(c,t,d,l);break}}return c}getAllParsedValues(t){const n=this._cachedMeta._parsed,i=[];let s,o,r;for(s=0,o=n.length;s=0&&tthis.getContext(i,s,n),m=c.resolveNamedOptions(f,d,g,u);return m.$shared&&(m.$shared=l,o[r]=Object.freeze(il(m,l))),m}_resolveAnimations(t,n,i){const s=this.chart,o=this._cachedDataOpts,r=`animation-${n}`,a=o[r];if(a)return a;let l;if(s.options.animation!==!1){const h=this.chart.config,u=h.datasetAnimationScopeKeys(this._type,n),f=h.getOptionScopes(this.getDataset(),u);l=h.createResolver(f,this.getContext(t,i,n))}const c=new Dh(s,l&&l.animations);return l&&l._cacheable&&(o[r]=Object.freeze(c)),c}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,n){return!n||tr(t)||this.chart._animationsDisabled}_getSharedOptions(t,n){const i=this.resolveDataElementOptions(t,n),s=this._sharedOptions,o=this.getSharedOptions(i),r=this.includeOptions(n,o)||o!==s;return this.updateSharedOptions(o,n,i),{sharedOptions:o,includeOptions:r}}updateElement(t,n,i,s){tr(s)?Object.assign(t,i):this._resolveAnimations(n,s).update(t,i)}updateSharedOptions(t,n,i){t&&!tr(n)&&this._resolveAnimations(void 0,n).update(t,i)}_setStyle(t,n,i,s){t.active=s;const o=this.getStyle(n,s);this._resolveAnimations(n,i,s).update(t,{options:!s&&this.getSharedOptions(o)||o})}removeHoverStyle(t,n,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,n,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const n=this._data,i=this._cachedMeta.data;for(const[a,l,c]of this._syncList)this[a](l,c);this._syncList=[];const s=i.length,o=n.length,r=Math.min(o,s);r&&this.parse(0,r),o>s?this._insertElements(s,o-s,t):o{for(c.length+=n,a=c.length-1;a>=r;a--)c[a]=c[a-n]};for(l(o),a=t;as-o))}return e._cache.$bar}function zm(e){const t=e.iScale,n=Fm(t,e.type);let i=t._length,s,o,r,a;const l=()=>{r===32767||r===-32768||(is(a)&&(i=Math.min(i,Math.abs(r-a)||i)),a=r)};for(s=0,o=n.length;s0?s[e-1]:null,a=eMath.abs(a)&&(l=a,c=r),t[n.axis]=c,t._custom={barStart:l,barEnd:c,start:s,end:o,min:r,max:a}}function Oh(e,t,n,i){return dt(e)?jm(e,t,n,i):t[n.axis]=n.parse(e,i),t}function sl(e,t,n,i){const s=e.iScale,o=e.vScale,r=s.getLabels(),a=s===o,l=[];let c,h,u,f;for(c=n,h=n+i;c=n?1:-1)}function Wm(e){let t,n,i,s,o;return e.horizontal?(t=e.base>e.x,n="left",i="right"):(t=e.baseh.controller.options.grouped),o=i.options.stacked,r=[],a=this._cachedMeta.controller.getParsed(n),l=a&&a[i.axis],c=h=>{const u=h._parsed.find(d=>d[i.axis]===l),f=u&&u[h.vScale.axis];if($(f)||isNaN(f))return!0};for(const h of s)if(!(n!==void 0&&c(h))&&((o===!1||r.indexOf(h.stack)===-1||o===void 0&&h.stack===void 0)&&r.push(h.stack),h.index===t))break;return r.length||r.push(void 0),r}_getStackCount(t){return this._getStacks(void 0,t).length}_getAxisCount(){return this._getAxis().length}getFirstScaleIdForIndexAxis(){const t=this.chart.scales,n=this.chart.options.indexAxis;return Object.keys(t).filter(i=>t[i].axis===n).shift()}_getAxis(){const t={},n=this.getFirstScaleIdForIndexAxis();for(const i of this.chart.data.datasets)t[W(this.chart.options.indexAxis==="x"?i.xAxisID:i.yAxisID,n)]=!0;return Object.keys(t)}_getStackIndex(t,n,i){const s=this._getStacks(t,i),o=n!==void 0?s.indexOf(n):-1;return o===-1?s.length-1:o}_getRuler(){const t=this.options,n=this._cachedMeta,i=n.iScale,s=[];let o,r;for(o=0,r=n.data.length;o=0;--i)n=Math.max(n,t[i].size(this.resolveDataElementOptions(i))/2);return n>0&&n}getLabelAndValue(t){const n=this._cachedMeta,i=this.chart.data.labels||[],{xScale:s,yScale:o}=n,r=this.getParsed(t),a=s.getLabelForValue(r.x),l=o.getLabelForValue(r.y),c=r._custom;return{label:i[t]||"",value:"("+a+", "+l+(c?", "+c:"")+")"}}update(t){const n=this._cachedMeta.data;this.updateElements(n,0,n.length,t)}updateElements(t,n,i,s){const o=s==="reset",{iScale:r,vScale:a}=this._cachedMeta,{sharedOptions:l,includeOptions:c}=this._getSharedOptions(n,s),h=r.axis,u=a.axis;for(let f=n;fss(x,a,l,!0)?1:Math.max(y,y*n,w,w*n),g=(x,y,w)=>ss(x,a,l,!0)?-1:Math.min(y,y*n,w,w*n),m=d(0,c,u),p=d(xt,h,f),v=g(st,c,u),_=g(st+xt,h,f);i=(m-v)/2,s=(p-_)/2,o=-(m+v)/2,r=-(p+_)/2}return{ratioX:i,ratioY:s,offsetX:o,offsetY:r}}class En extends we{constructor(t,n){super(t,n),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,n){const i=this.getDataset().data,s=this._cachedMeta;if(this._parsing===!1)s._parsed=i;else{let o=l=>+i[l];if(Z(i[t])){const{key:l="value"}=this._parsing;o=c=>+bn(i[c],l)}let r,a;for(r=t,a=t+n;r0&&!isNaN(t)?gt*(Math.abs(t)/n):0}getLabelAndValue(t){const n=this._cachedMeta,i=this.chart,s=i.data.labels||[],o=ws(n._parsed[t],i.options.locale);return{label:s[t]||"",value:o}}getMaxBorderWidth(t){let n=0;const i=this.chart;let s,o,r,a,l;if(!t){for(s=0,o=i.data.datasets.length;st!=="spacing",_indexable:t=>t!=="spacing"&&!t.startsWith("borderDash")&&!t.startsWith("hoverBorderDash")}),D(En,"overrides",{aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const n=t.data,{labels:{pointStyle:i,textAlign:s,color:o,useBorderRadius:r,borderRadius:a}}=t.legend.options;return n.labels.length&&n.datasets.length?n.labels.map((l,c)=>{const u=t.getDatasetMeta(0).controller.getStyle(c);return{text:l,fillStyle:u.backgroundColor,fontColor:o,hidden:!t.getDataVisibility(c),lineDash:u.borderDash,lineDashOffset:u.borderDashOffset,lineJoin:u.borderJoinStyle,lineWidth:u.borderWidth,strokeStyle:u.borderColor,textAlign:s,pointStyle:i,borderRadius:r&&(a||u.borderRadius),index:c}}):[]}},onClick(t,n,i){i.chart.toggleDataVisibility(n.index),i.chart.update()}}}});class so extends we{initialize(){this.enableOptionSharing=!0,this.supportsDecimation=!0,super.initialize()}update(t){const n=this._cachedMeta,{dataset:i,data:s=[],_dataset:o}=n,r=this.chart._animationsDisabled;let{start:a,count:l}=fh(n,s,r);this._drawStart=a,this._drawCount=l,dh(n)&&(a=0,l=s.length),i._chart=this.chart,i._datasetIndex=this.index,i._decimated=!!o._decimated,i.points=s;const c=this.resolveDatasetElementOptions(t);this.options.showLine||(c.borderWidth=0),c.segment=this.options.segment,this.updateElement(i,void 0,{animated:!r,options:c},t),this.updateElements(s,a,l,t)}updateElements(t,n,i,s){const o=s==="reset",{iScale:r,vScale:a,_stacked:l,_dataset:c}=this._cachedMeta,{sharedOptions:h,includeOptions:u}=this._getSharedOptions(n,s),f=r.axis,d=a.axis,{spanGaps:g,segment:m}=this.options,p=xi(g)?g:Number.POSITIVE_INFINITY,v=this.chart._animationsDisabled||o||s==="none",_=n+i,x=t.length;let y=n>0&&this.getParsed(n-1);for(let w=0;w=_){M.skip=!0;continue}const P=this.getParsed(w),E=$(P[d]),I=M[f]=r.getPixelForValue(P[f],w),j=M[d]=o||E?a.getBasePixel():a.getPixelForValue(l?this.applyStack(a,P,l):P[d],w);M.skip=isNaN(I)||isNaN(j)||E,M.stop=w>0&&Math.abs(P[f]-y[f])>p,m&&(M.parsed=P,M.raw=c.data[w]),u&&(M.options=h||this.resolveDataElementOptions(w,S.active?"active":s)),v||this.updateElement(S,w,M,s),y=P}}getMaxOverflow(){const t=this._cachedMeta,n=t.dataset,i=n.options&&n.options.borderWidth||0,s=t.data||[];if(!s.length)return i;const o=s[0].size(this.resolveDataElementOptions(0)),r=s[s.length-1].size(this.resolveDataElementOptions(s.length-1));return Math.max(i,o,r)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}}D(so,"id","line"),D(so,"defaults",{datasetElementType:"line",dataElementType:"point",showLine:!0,spanGaps:!1}),D(so,"overrides",{scales:{_index_:{type:"category"},_value_:{type:"linear"}}});class Ki extends we{constructor(t,n){super(t,n),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const n=this._cachedMeta,i=this.chart,s=i.data.labels||[],o=ws(n._parsed[t].r,i.options.locale);return{label:s[t]||"",value:o}}parseObjectData(t,n,i,s){return yh.bind(this)(t,n,i,s)}update(t){const n=this._cachedMeta.data;this._updateRadius(),this.updateElements(n,0,n.length,t)}getMinMax(){const t=this._cachedMeta,n={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY};return t.data.forEach((i,s)=>{const o=this.getParsed(s).r;!isNaN(o)&&this.chart.getDataVisibility(s)&&(on.max&&(n.max=o))}),n}_updateRadius(){const t=this.chart,n=t.chartArea,i=t.options,s=Math.min(n.right-n.left,n.bottom-n.top),o=Math.max(s/2,0),r=Math.max(i.cutoutPercentage?o/100*i.cutoutPercentage:1,0),a=(o-r)/t.getVisibleDatasetCount();this.outerRadius=o-a*this.index,this.innerRadius=this.outerRadius-a}updateElements(t,n,i,s){const o=s==="reset",r=this.chart,l=r.options.animation,c=this._cachedMeta.rScale,h=c.xCenter,u=c.yCenter,f=c.getIndexAngle(0)-.5*st;let d=f,g;const m=360/this.countVisibleElements();for(g=0;g{!isNaN(this.getParsed(s).r)&&this.chart.getDataVisibility(s)&&n++}),n}_computeAngle(t,n,i){return this.chart.getDataVisibility(t)?be(this.resolveDataElementOptions(t,n).angle||i):0}}D(Ki,"id","polarArea"),D(Ki,"defaults",{dataElementType:"arc",animation:{animateRotate:!0,animateScale:!0},animations:{numbers:{type:"number",properties:["x","y","startAngle","endAngle","innerRadius","outerRadius"]}},indexAxis:"r",startAngle:0}),D(Ki,"overrides",{aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const n=t.data;if(n.labels.length&&n.datasets.length){const{labels:{pointStyle:i,color:s}}=t.legend.options;return n.labels.map((o,r)=>{const l=t.getDatasetMeta(0).controller.getStyle(r);return{text:o,fillStyle:l.backgroundColor,strokeStyle:l.borderColor,fontColor:s,lineWidth:l.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(r),index:r}})}return[]}},onClick(t,n,i){i.chart.toggleDataVisibility(n.index),i.chart.update()}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}});class Dr extends En{}D(Dr,"id","pie"),D(Dr,"defaults",{cutout:0,rotation:0,circumference:360,radius:"100%"});class oo extends we{getLabelAndValue(t){const n=this._cachedMeta.vScale,i=this.getParsed(t);return{label:n.getLabels()[t],value:""+n.getLabelForValue(i[n.axis])}}parseObjectData(t,n,i,s){return yh.bind(this)(t,n,i,s)}update(t){const n=this._cachedMeta,i=n.dataset,s=n.data||[],o=n.iScale.getLabels();if(i.points=s,t!=="resize"){const r=this.resolveDatasetElementOptions(t);this.options.showLine||(r.borderWidth=0);const a={_loop:!0,_fullLoop:o.length===s.length,options:r};this.updateElement(i,void 0,a,t)}this.updateElements(s,0,s.length,t)}updateElements(t,n,i,s){const o=this._cachedMeta.rScale,r=s==="reset";for(let a=n;a0&&this.getParsed(n-1);for(let y=n;y0&&Math.abs(S[d]-x[d])>v,p&&(M.parsed=S,M.raw=c.data[y]),f&&(M.options=u||this.resolveDataElementOptions(y,w.active?"active":s)),_||this.updateElement(w,y,M,s),x=S}this.updateSharedOptions(u,s,h)}getMaxOverflow(){const t=this._cachedMeta,n=t.data||[];if(!this.options.showLine){let a=0;for(let l=n.length-1;l>=0;--l)a=Math.max(a,n[l].size(this.resolveDataElementOptions(l))/2);return a>0&&a}const i=t.dataset,s=i.options&&i.options.borderWidth||0;if(!n.length)return s;const o=n[0].size(this.resolveDataElementOptions(0)),r=n[n.length-1].size(this.resolveDataElementOptions(n.length-1));return Math.max(s,o,r)/2}}D(ro,"id","scatter"),D(ro,"defaults",{datasetElementType:!1,dataElementType:"point",showLine:!1,fill:!1}),D(ro,"overrides",{interaction:{mode:"point"},scales:{x:{type:"linear"},y:{type:"linear"}}});var qm=Object.freeze({__proto__:null,BarController:no,BubbleController:io,DoughnutController:En,LineController:so,PieController:Dr,PolarAreaController:Ki,RadarController:oo,ScatterController:ro});function Sn(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class pa{constructor(t){D(this,"options");this.options=t||{}}static override(t){Object.assign(pa.prototype,t)}init(){}formats(){return Sn()}parse(){return Sn()}format(){return Sn()}add(){return Sn()}diff(){return Sn()}startOf(){return Sn()}endOf(){return Sn()}}var $m={_date:pa};function Km(e,t,n,i){const{controller:s,data:o,_sorted:r}=e,a=s._cachedMeta.iScale,l=e.dataset&&e.dataset.options?e.dataset.options.spanGaps:null;if(a&&t===a.axis&&t!=="r"&&r&&o.length){const c=a._reversePixels?vp:Xe;if(i){if(s._sharedOptions){const h=o[0],u=typeof h.getRange=="function"&&h.getRange(t);if(u){const f=c(o,t,n-u),d=c(o,t,n+u);return{lo:f.lo,hi:d.hi}}}}else{const h=c(o,t,n);if(l){const{vScale:u}=s._cachedMeta,{_parsed:f}=e,d=f.slice(0,h.lo+1).reverse().findIndex(m=>!$(m[u.axis]));h.lo-=Math.max(0,d);const g=f.slice(h.hi).findIndex(m=>!$(m[u.axis]));h.hi+=Math.max(0,g)}return h}}return{lo:0,hi:o.length-1}}function Fo(e,t,n,i,s){const o=e.getSortedVisibleDatasetMetas(),r=n[t];for(let a=0,l=o.length;a{l[r]&&l[r](t[n],s)&&(o.push({element:l,datasetIndex:c,index:h}),a=a||l.inRange(t.x,t.y,s))}),i&&!a?[]:o}var Qm={modes:{index(e,t,n,i){const s=Cn(t,e),o=n.axis||"x",r=n.includeInvisible||!1,a=n.intersect?nr(e,s,o,i,r):ir(e,s,o,!1,i,r),l=[];return a.length?(e.getSortedVisibleDatasetMetas().forEach(c=>{const h=a[0].index,u=c.data[h];u&&!u.skip&&l.push({element:u,datasetIndex:c.index,index:h})}),l):[]},dataset(e,t,n,i){const s=Cn(t,e),o=n.axis||"xy",r=n.includeInvisible||!1;let a=n.intersect?nr(e,s,o,i,r):ir(e,s,o,!1,i,r);if(a.length>0){const l=a[0].datasetIndex,c=e.getDatasetMeta(l).data;a=[];for(let h=0;hn.pos===t)}function ll(e,t){return e.filter(n=>Eh.indexOf(n.pos)===-1&&n.box.axis===t)}function Ti(e,t){return e.sort((n,i)=>{const s=t?i:n,o=t?n:i;return s.weight===o.weight?s.index-o.index:s.weight-o.weight})}function tv(e){const t=[];let n,i,s,o,r,a;for(n=0,i=(e||[]).length;nc.box.fullSize),!0),i=Ti(Di(t,"left"),!0),s=Ti(Di(t,"right")),o=Ti(Di(t,"top"),!0),r=Ti(Di(t,"bottom")),a=ll(t,"x"),l=ll(t,"y");return{fullSize:n,leftAndTop:i.concat(o),rightAndBottom:s.concat(l).concat(r).concat(a),chartArea:Di(t,"chartArea"),vertical:i.concat(s).concat(l),horizontal:o.concat(r).concat(a)}}function cl(e,t,n,i){return Math.max(e[n],t[n])+Math.max(e[i],t[i])}function Lh(e,t){e.top=Math.max(e.top,t.top),e.left=Math.max(e.left,t.left),e.bottom=Math.max(e.bottom,t.bottom),e.right=Math.max(e.right,t.right)}function sv(e,t,n,i){const{pos:s,box:o}=n,r=e.maxPadding;if(!Z(s)){n.size&&(e[s]-=n.size);const u=i[n.stack]||{size:0,count:1};u.size=Math.max(u.size,n.horizontal?o.height:o.width),n.size=u.size/u.count,e[s]+=n.size}o.getPadding&&Lh(r,o.getPadding());const a=Math.max(0,t.outerWidth-cl(r,e,"left","right")),l=Math.max(0,t.outerHeight-cl(r,e,"top","bottom")),c=a!==e.w,h=l!==e.h;return e.w=a,e.h=l,n.horizontal?{same:c,other:h}:{same:h,other:c}}function ov(e){const t=e.maxPadding;function n(i){const s=Math.max(t[i]-e[i],0);return e[i]+=s,s}e.y+=n("top"),e.x+=n("left"),n("right"),n("bottom")}function rv(e,t){const n=t.maxPadding;function i(s){const o={left:0,top:0,right:0,bottom:0};return s.forEach(r=>{o[r]=Math.max(t[r],n[r])}),o}return i(e?["left","right"]:["top","bottom"])}function Ni(e,t,n,i){const s=[];let o,r,a,l,c,h;for(o=0,r=e.length,c=0;o{typeof m.beforeLayout=="function"&&m.beforeLayout()});const h=l.reduce((m,p)=>p.box.options&&p.box.options.display===!1?m:m+1,0)||1,u=Object.freeze({outerWidth:t,outerHeight:n,padding:s,availableWidth:o,availableHeight:r,vBoxMaxWidth:o/2/h,hBoxMaxHeight:r/2}),f=Object.assign({},s);Lh(f,Et(i));const d=Object.assign({maxPadding:f,w:o,h:r,x:s.left,y:s.top},s),g=nv(l.concat(c),u);Ni(a.fullSize,d,u,g),Ni(l,d,u,g),Ni(c,d,u,g)&&Ni(l,d,u,g),ov(d),hl(a.leftAndTop,d,u,g),d.x+=d.w,d.y+=d.h,hl(a.rightAndBottom,d,u,g),e.chartArea={left:d.left,top:d.top,right:d.left+d.w,bottom:d.top+d.h,height:d.h,width:d.w},ct(a.chartArea,m=>{const p=m.box;Object.assign(p,e.chartArea),p.update(d.w,d.h,{left:0,top:0,right:0,bottom:0})})}};class Rh{acquireContext(t,n){}releaseContext(t){return!1}addEventListener(t,n,i){}removeEventListener(t,n,i){}getDevicePixelRatio(){return 1}getMaximumSize(t,n,i,s){return n=Math.max(0,n||t.width),i=i||t.height,{width:n,height:Math.max(0,s?Math.floor(n/s):i)}}isAttached(t){return!0}updateConfig(t){}}class av extends Rh{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}}const ao="$chartjs",lv={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},ul=e=>e===null||e==="";function cv(e,t){const n=e.style,i=e.getAttribute("height"),s=e.getAttribute("width");if(e[ao]={initial:{height:i,width:s,style:{display:n.display,height:n.height,width:n.width}}},n.display=n.display||"block",n.boxSizing=n.boxSizing||"border-box",ul(s)){const o=$a(e,"width");o!==void 0&&(e.width=o)}if(ul(i))if(e.style.height==="")e.height=e.width/(t||2);else{const o=$a(e,"height");o!==void 0&&(e.height=o)}return e}const Ih=hm?{passive:!0}:!1;function hv(e,t,n){e&&e.addEventListener(t,n,Ih)}function uv(e,t,n){e&&e.canvas&&e.canvas.removeEventListener(t,n,Ih)}function fv(e,t){const n=lv[e.type]||e.type,{x:i,y:s}=Cn(e,t);return{type:n,chart:t,native:e,x:i!==void 0?i:null,y:s!==void 0?s:null}}function yo(e,t){for(const n of e)if(n===t||n.contains(t))return!0}function dv(e,t,n){const i=e.canvas,s=new MutationObserver(o=>{let r=!1;for(const a of o)r=r||yo(a.addedNodes,i),r=r&&!yo(a.removedNodes,i);r&&n()});return s.observe(document,{childList:!0,subtree:!0}),s}function gv(e,t,n){const i=e.canvas,s=new MutationObserver(o=>{let r=!1;for(const a of o)r=r||yo(a.removedNodes,i),r=r&&!yo(a.addedNodes,i);r&&n()});return s.observe(document,{childList:!0,subtree:!0}),s}const rs=new Map;let fl=0;function Fh(){const e=window.devicePixelRatio;e!==fl&&(fl=e,rs.forEach((t,n)=>{n.currentDevicePixelRatio!==e&&t()}))}function pv(e,t){rs.size||window.addEventListener("resize",Fh),rs.set(e,t)}function mv(e){rs.delete(e),rs.size||window.removeEventListener("resize",Fh)}function vv(e,t,n){const i=e.canvas,s=i&&ga(i);if(!s)return;const o=uh((a,l)=>{const c=s.clientWidth;n(a,l),c{const l=a[0],c=l.contentRect.width,h=l.contentRect.height;c===0&&h===0||o(c,h)});return r.observe(s),pv(e,o),r}function sr(e,t,n){n&&n.disconnect(),t==="resize"&&mv(e)}function _v(e,t,n){const i=e.canvas,s=uh(o=>{e.ctx!==null&&n(fv(o,e))},e);return hv(i,t,s),s}class bv extends Rh{acquireContext(t,n){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(cv(t,n),i):null}releaseContext(t){const n=t.canvas;if(!n[ao])return!1;const i=n[ao].initial;["height","width"].forEach(o=>{const r=i[o];$(r)?n.removeAttribute(o):n.setAttribute(o,r)});const s=i.style||{};return Object.keys(s).forEach(o=>{n.style[o]=s[o]}),n.width=n.width,delete n[ao],!0}addEventListener(t,n,i){this.removeEventListener(t,n);const s=t.$proxies||(t.$proxies={}),r={attach:dv,detach:gv,resize:vv}[n]||_v;s[n]=r(t,n,i)}removeEventListener(t,n){const i=t.$proxies||(t.$proxies={}),s=i[n];if(!s)return;({attach:sr,detach:sr,resize:sr}[n]||uv)(t,n,s),i[n]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,n,i,s){return cm(t,n,i,s)}isAttached(t){const n=t&&ga(t);return!!(n&&n.isConnected)}}function xv(e){return!da()||typeof OffscreenCanvas<"u"&&e instanceof OffscreenCanvas?av:bv}var Xs;let tn=(Xs=class{constructor(){D(this,"x");D(this,"y");D(this,"active",!1);D(this,"options");D(this,"$animations")}tooltipPosition(t){const{x:n,y:i}=this.getProps(["x","y"],t);return{x:n,y:i}}hasValue(){return xi(this.x)&&xi(this.y)}getProps(t,n){const i=this.$animations;if(!n||!i)return this;const s={};return t.forEach(o=>{s[o]=i[o]&&i[o].active()?i[o]._to:this[o]}),s}},D(Xs,"defaults",{}),D(Xs,"defaultRoutes"),Xs);function yv(e,t){const n=e.options.ticks,i=wv(e),s=Math.min(n.maxTicksLimit||i,i),o=n.major.enabled?Mv(t):[],r=o.length,a=o[0],l=o[r-1],c=[];if(r>s)return Sv(t,c,o,r/s),c;const h=kv(o,t,s);if(r>0){let u,f;const d=r>1?Math.round((l-a)/(r-1)):null;for(Is(t,c,h,$(d)?0:a-d,a),u=0,f=r-1;us)return l}return Math.max(s,1)}function Mv(e){const t=[];let n,i;for(n=0,i=e.length;ne==="left"?"right":e==="right"?"left":e,dl=(e,t,n)=>t==="top"||t==="left"?e[t]+n:e[t]-n,gl=(e,t)=>Math.min(t||e,e);function pl(e,t){const n=[],i=e.length/t,s=e.length;let o=0;for(;or+a)))return l}function Dv(e,t){ct(e,n=>{const i=n.gc,s=i.length/2;let o;if(s>t){for(o=0;oi?i:n,i=s&&n>i?n:i,{min:Gt(n,Gt(i,n)),max:Gt(i,Gt(n,i))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}getLabelItems(t=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(t))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){ht(this.options.beforeUpdate,[this])}update(t,n,i){const{beginAtZero:s,grace:o,ticks:r}=this.options,a=r.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=n,this._margins=i=Object.assign({left:0,right:0,top:0,bottom:0},i),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+i.left+i.right:this.height+i.top+i.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=Vp(this,o,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const l=a=o||i<=1||!this.isHorizontal()){this.labelRotation=s;return}const h=this._getLabelSizes(),u=h.widest.width,f=h.highest.height,d=At(this.chart.width-u,0,this.maxWidth);a=t.offset?this.maxWidth/i:d/(i-1),u+6>a&&(a=d/(i-(t.offset?.5:1)),l=this.maxHeight-Oi(t.grid)-n.padding-ml(t.title,this.chart.options.font),c=Math.sqrt(u*u+f*f),r=oa(Math.min(Math.asin(At((h.highest.height+6)/a,-1,1)),Math.asin(At(l/c,-1,1))-Math.asin(At(f/c,-1,1)))),r=Math.max(s,Math.min(o,r))),this.labelRotation=r}afterCalculateLabelRotation(){ht(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){ht(this.options.beforeFit,[this])}fit(){const t={width:0,height:0},{chart:n,options:{ticks:i,title:s,grid:o}}=this,r=this._isVisible(),a=this.isHorizontal();if(r){const l=ml(s,n.options.font);if(a?(t.width=this.maxWidth,t.height=Oi(o)+l):(t.height=this.maxHeight,t.width=Oi(o)+l),i.display&&this.ticks.length){const{first:c,last:h,widest:u,highest:f}=this._getLabelSizes(),d=i.padding*2,g=be(this.labelRotation),m=Math.cos(g),p=Math.sin(g);if(a){const v=i.mirror?0:p*u.width+m*f.height;t.height=Math.min(this.maxHeight,t.height+v+d)}else{const v=i.mirror?0:m*u.width+p*f.height;t.width=Math.min(this.maxWidth,t.width+v+d)}this._calculatePadding(c,h,p,m)}}this._handleMargins(),a?(this.width=this._length=n.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=n.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,n,i,s){const{ticks:{align:o,padding:r},position:a}=this.options,l=this.labelRotation!==0,c=a!=="top"&&this.axis==="x";if(this.isHorizontal()){const h=this.getPixelForTick(0)-this.left,u=this.right-this.getPixelForTick(this.ticks.length-1);let f=0,d=0;l?c?(f=s*t.width,d=i*n.height):(f=i*t.height,d=s*n.width):o==="start"?d=n.width:o==="end"?f=t.width:o!=="inner"&&(f=t.width/2,d=n.width/2),this.paddingLeft=Math.max((f-h+r)*this.width/(this.width-h),0),this.paddingRight=Math.max((d-u+r)*this.width/(this.width-u),0)}else{let h=n.height/2,u=t.height/2;o==="start"?(h=0,u=t.height):o==="end"&&(h=n.height,u=0),this.paddingTop=h+r,this.paddingBottom=u+r}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){ht(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:n}=this.options;return n==="top"||n==="bottom"||t==="x"}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){this.beforeTickToLabelConversion(),this.generateTickLabels(t);let n,i;for(n=0,i=t.length;n({width:r[E]||0,height:a[E]||0});return{first:P(0),last:P(n-1),widest:P(S),highest:P(M),widths:r,heights:a}}getLabelForValue(t){return t}getPixelForValue(t,n){return NaN}getValueForPixel(t){}getPixelForTick(t){const n=this.ticks;return t<0||t>n.length-1?null:this.getPixelForValue(n[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);const n=this._startPixel+t*this._length;return mp(this._alignToPixels?Mn(this.chart,n,0):n)}getDecimalForPixel(t){const n=(t-this._startPixel)/this._length;return this._reversePixels?1-n:n}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:n}=this;return t<0&&n<0?n:t>0&&n>0?t:0}getContext(t){const n=this.ticks||[];if(t>=0&&ta*s?a/i:l/s:l*s0}_computeGridLineItems(t){const n=this.axis,i=this.chart,s=this.options,{grid:o,position:r,border:a}=s,l=o.offset,c=this.isHorizontal(),u=this.ticks.length+(l?1:0),f=Oi(o),d=[],g=a.setContext(this.getContext()),m=g.display?g.width:0,p=m/2,v=function(H){return Mn(i,H,m)};let _,x,y,w,S,M,P,E,I,j,O,F;if(r==="top")_=v(this.bottom),M=this.bottom-f,E=_-p,j=v(t.top)+p,F=t.bottom;else if(r==="bottom")_=v(this.top),j=t.top,F=v(t.bottom)-p,M=_+p,E=this.top+f;else if(r==="left")_=v(this.right),S=this.right-f,P=_-p,I=v(t.left)+p,O=t.right;else if(r==="right")_=v(this.left),I=t.left,O=v(t.right)-p,S=_+p,P=this.left+f;else if(n==="x"){if(r==="center")_=v((t.top+t.bottom)/2+.5);else if(Z(r)){const H=Object.keys(r)[0],K=r[H];_=v(this.chart.scales[H].getPixelForValue(K))}j=t.top,F=t.bottom,M=_+p,E=M+f}else if(n==="y"){if(r==="center")_=v((t.left+t.right)/2);else if(Z(r)){const H=Object.keys(r)[0],K=r[H];_=v(this.chart.scales[H].getPixelForValue(K))}S=_-p,P=S-f,I=t.left,O=t.right}const V=W(s.ticks.maxTicksLimit,u),z=Math.max(1,Math.ceil(u/V));for(x=0;x0&&(wn-=$t/2);break}te={left:wn,top:Pi,width:$t+ee.width,height:Lt+ee.height,color:z.backdropColor}}p.push({label:y,font:E,textOffset:O,options:{rotation:m,color:K,strokeColor:et,strokeWidth:U,textAlign:kt,textBaseline:F,translation:[w,S],backdrop:te}})}return p}_getXAxisLabelAlignment(){const{position:t,ticks:n}=this.options;if(-be(this.labelRotation))return t==="top"?"left":"right";let s="center";return n.align==="start"?s="left":n.align==="end"?s="right":n.align==="inner"&&(s="inner"),s}_getYAxisLabelAlignment(t){const{position:n,ticks:{crossAlign:i,mirror:s,padding:o}}=this.options,r=this._getLabelSizes(),a=t+o,l=r.widest.width;let c,h;return n==="left"?s?(h=this.right+o,i==="near"?c="left":i==="center"?(c="center",h+=l/2):(c="right",h+=l)):(h=this.right-a,i==="near"?c="right":i==="center"?(c="center",h-=l/2):(c="left",h=this.left)):n==="right"?s?(h=this.left+o,i==="near"?c="right":i==="center"?(c="center",h-=l/2):(c="left",h-=l)):(h=this.left+a,i==="near"?c="left":i==="center"?(c="center",h+=l/2):(c="right",h=this.right)):c="right",{textAlign:c,x:h}}_computeLabelArea(){if(this.options.ticks.mirror)return;const t=this.chart,n=this.options.position;if(n==="left"||n==="right")return{top:0,left:this.left,bottom:t.height,right:this.right};if(n==="top"||n==="bottom")return{top:this.top,left:0,bottom:this.bottom,right:t.width}}drawBackground(){const{ctx:t,options:{backgroundColor:n},left:i,top:s,width:o,height:r}=this;n&&(t.save(),t.fillStyle=n,t.fillRect(i,s,o,r),t.restore())}getLineWidthForValue(t){const n=this.options.grid;if(!this._isVisible()||!n.display)return 0;const s=this.ticks.findIndex(o=>o.value===t);return s>=0?n.setContext(this.getContext(s)).lineWidth:0}drawGrid(t){const n=this.options.grid,i=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t));let o,r;const a=(l,c,h)=>{!h.width||!h.color||(i.save(),i.lineWidth=h.width,i.strokeStyle=h.color,i.setLineDash(h.borderDash||[]),i.lineDashOffset=h.borderDashOffset,i.beginPath(),i.moveTo(l.x,l.y),i.lineTo(c.x,c.y),i.stroke(),i.restore())};if(n.display)for(o=0,r=s.length;o{this.draw(o)}}]:[{z:i,draw:o=>{this.drawBackground(),this.drawGrid(o),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:n,draw:o=>{this.drawLabels(o)}}]}getMatchingVisibleMetas(t){const n=this.chart.getSortedVisibleDatasetMetas(),i=this.axis+"AxisID",s=[];let o,r;for(o=0,r=n.length;o{const i=n.split("."),s=i.pop(),o=[e].concat(i).join("."),r=t[n].split("."),a=r.pop(),l=r.join(".");pt.route(o,s,l,a)})}function Fv(e){return"id"in e&&"defaults"in e}class zv{constructor(){this.controllers=new Fs(we,"datasets",!0),this.elements=new Fs(tn,"elements"),this.plugins=new Fs(Object,"plugins"),this.scales=new Fs(ti,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,n,i){[...n].forEach(s=>{const o=i||this._getRegistryForType(s);i||o.isForType(s)||o===this.plugins&&s.id?this._exec(t,o,s):ct(s,r=>{const a=i||this._getRegistryForType(r);this._exec(t,a,r)})})}_exec(t,n,i){const s=sa(t);ht(i["before"+s],[],i),n[t](i),ht(i["after"+s],[],i)}_getRegistryForType(t){for(let n=0;no.filter(a=>!r.some(l=>a.plugin.id===l.plugin.id));this._notify(s(n,i),t,"stop"),this._notify(s(i,n),t,"start")}}function Bv(e){const t={},n=[],i=Object.keys(Ce.plugins.items);for(let o=0;o1&&vl(e[0].toLowerCase());if(i)return i}throw new Error(`Cannot determine type of '${e}' axis. Please provide 'axis' or 'position' option.`)}function _l(e,t,n){if(n[t+"AxisID"]===e)return{axis:t}}function Xv(e,t){if(t.data&&t.data.datasets){const n=t.data.datasets.filter(i=>i.xAxisID===e||i.yAxisID===e);if(n.length)return _l(e,"x",n[0])||_l(e,"y",n[0])}return{}}function qv(e,t){const n=Jn[e.type]||{scales:{}},i=t.scales||{},s=Tr(e.type,t),o=Object.create(null);return Object.keys(i).forEach(r=>{const a=i[r];if(!Z(a))return console.error(`Invalid scale configuration for scale: ${r}`);if(a._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${r}`);const l=Or(r,a,Xv(r,e),pt.scales[a.type]),c=Yv(l,s),h=n.scales||{};o[r]=Ui(Object.create(null),[{axis:l},a,h[l],h[c]])}),e.data.datasets.forEach(r=>{const a=r.type||e.type,l=r.indexAxis||Tr(a,t),h=(Jn[a]||{}).scales||{};Object.keys(h).forEach(u=>{const f=Hv(u,l),d=r[f+"AxisID"]||f;o[d]=o[d]||Object.create(null),Ui(o[d],[{axis:f},i[d],h[u]])})}),Object.keys(o).forEach(r=>{const a=o[r];Ui(a,[pt.scales[a.type],pt.scale])}),o}function zh(e){const t=e.options||(e.options={});t.plugins=W(t.plugins,{}),t.scales=qv(e,t)}function Nh(e){return e=e||{},e.datasets=e.datasets||[],e.labels=e.labels||[],e}function $v(e){return e=e||{},e.data=Nh(e.data),zh(e),e}const bl=new Map,Bh=new Set;function zs(e,t){let n=bl.get(e);return n||(n=t(),bl.set(e,n),Bh.add(n)),n}const Ei=(e,t,n)=>{const i=bn(t,n);i!==void 0&&e.add(i)};class Kv{constructor(t){this._config=$v(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=Nh(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),zh(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return zs(t,()=>[[`datasets.${t}`,""]])}datasetAnimationScopeKeys(t,n){return zs(`${t}.transition.${n}`,()=>[[`datasets.${t}.transitions.${n}`,`transitions.${n}`],[`datasets.${t}`,""]])}datasetElementScopeKeys(t,n){return zs(`${t}-${n}`,()=>[[`datasets.${t}.elements.${n}`,`datasets.${t}`,`elements.${n}`,""]])}pluginScopeKeys(t){const n=t.id,i=this.type;return zs(`${i}-plugin-${n}`,()=>[[`plugins.${n}`,...t.additionalOptionScopes||[]]])}_cachedScopes(t,n){const i=this._scopeCache;let s=i.get(t);return(!s||n)&&(s=new Map,i.set(t,s)),s}getOptionScopes(t,n,i){const{options:s,type:o}=this,r=this._cachedScopes(t,i),a=r.get(n);if(a)return a;const l=new Set;n.forEach(h=>{t&&(l.add(t),h.forEach(u=>Ei(l,t,u))),h.forEach(u=>Ei(l,s,u)),h.forEach(u=>Ei(l,Jn[o]||{},u)),h.forEach(u=>Ei(l,pt,u)),h.forEach(u=>Ei(l,Cr,u))});const c=Array.from(l);return c.length===0&&c.push(Object.create(null)),Bh.has(n)&&r.set(n,c),c}chartOptionScopes(){const{options:t,type:n}=this;return[t,Jn[n]||{},pt.datasets[n]||{},{type:n},pt,Cr]}resolveNamedOptions(t,n,i,s=[""]){const o={$shared:!0},{resolver:r,subPrefixes:a}=xl(this._resolverCache,t,s);let l=r;if(Jv(r,n)){o.$shared=!1,i=xn(i)?i():i;const c=this.createResolver(t,i,a);l=yi(r,i,c)}for(const c of n)o[c]=l[c];return o}createResolver(t,n,i=[""],s){const{resolver:o}=xl(this._resolverCache,t,i);return Z(n)?yi(o,n,void 0,s):o}}function xl(e,t,n){let i=e.get(t);i||(i=new Map,e.set(t,i));const s=n.join();let o=i.get(s);return o||(o={resolver:ha(t,n),subPrefixes:n.filter(a=>!a.toLowerCase().includes("hover"))},i.set(s,o)),o}const Gv=e=>Z(e)&&Object.getOwnPropertyNames(e).some(t=>xn(e[t]));function Jv(e,t){const{isScriptable:n,isIndexable:i}=vh(e);for(const s of t){const o=n(s),r=i(s),a=(r||o)&&e[s];if(o&&(xn(a)||Gv(a))||r&&dt(a))return!0}return!1}var Zv="4.5.1";const Qv=["top","bottom","left","right","chartArea"];function yl(e,t){return e==="top"||e==="bottom"||Qv.indexOf(e)===-1&&t==="x"}function wl(e,t){return function(n,i){return n[e]===i[e]?n[t]-i[t]:n[e]-i[e]}}function kl(e){const t=e.chart,n=t.options.animation;t.notifyPlugins("afterRender"),ht(n&&n.onComplete,[e],t)}function t_(e){const t=e.chart,n=t.options.animation;ht(n&&n.onProgress,[e],t)}function jh(e){return da()&&typeof e=="string"?e=document.getElementById(e):e&&e.length&&(e=e[0]),e&&e.canvas&&(e=e.canvas),e}const lo={},Ml=e=>{const t=jh(e);return Object.values(lo).filter(n=>n.canvas===t).pop()};function e_(e,t,n){const i=Object.keys(e);for(const s of i){const o=+s;if(o>=t){const r=e[s];delete e[s],(n>0||o>t)&&(e[o+n]=r)}}}function n_(e,t,n,i){return!n||e.type==="mouseout"?null:i?t:e}class Ve{static register(...t){Ce.add(...t),Sl()}static unregister(...t){Ce.remove(...t),Sl()}constructor(t,n){const i=this.config=new Kv(n),s=jh(t),o=Ml(s);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");const r=i.createResolver(i.chartOptionScopes(),this.getContext());this.platform=new(i.platform||xv(s)),this.platform.updateConfig(i);const a=this.platform.acquireContext(s,r.aspectRatio),l=a&&a.canvas,c=l&&l.height,h=l&&l.width;if(this.id=ip(),this.ctx=a,this.canvas=l,this.width=h,this.height=c,this._options=r,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new Nv,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=xp(u=>this.update(u),r.resizeDelay||0),this._dataChanges=[],lo[this.id]=this,!a||!l){console.error("Failed to create chart: can't acquire context from the given item");return}Ne.listen(this,"complete",kl),Ne.listen(this,"progress",t_),this._initialize(),this.attached&&this.update()}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:n},width:i,height:s,_aspectRatio:o}=this;return $(t)?n&&o?o:s?i/s:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}get registry(){return Ce}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():qa(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return Ya(this.canvas,this.ctx),this}stop(){return Ne.stop(this),this}resize(t,n){Ne.running(this)?this._resizeBeforeDraw={width:t,height:n}:this._resize(t,n)}_resize(t,n){const i=this.options,s=this.canvas,o=i.maintainAspectRatio&&this.aspectRatio,r=this.platform.getMaximumSize(s,t,n,o),a=i.devicePixelRatio||this.platform.getDevicePixelRatio(),l=this.width?"resize":"attach";this.width=r.width,this.height=r.height,this._aspectRatio=this.aspectRatio,qa(this,a,!0)&&(this.notifyPlugins("resize",{size:r}),ht(i.onResize,[this,r],this),this.attached&&this._doResize(l)&&this.render())}ensureScalesHaveIDs(){const n=this.options.scales||{};ct(n,(i,s)=>{i.id=s})}buildOrUpdateScales(){const t=this.options,n=t.scales,i=this.scales,s=Object.keys(i).reduce((r,a)=>(r[a]=!1,r),{});let o=[];n&&(o=o.concat(Object.keys(n).map(r=>{const a=n[r],l=Or(r,a),c=l==="r",h=l==="x";return{options:a,dposition:c?"chartArea":h?"bottom":"left",dtype:c?"radialLinear":h?"category":"linear"}}))),ct(o,r=>{const a=r.options,l=a.id,c=Or(l,a),h=W(a.type,r.dtype);(a.position===void 0||yl(a.position,c)!==yl(r.dposition))&&(a.position=r.dposition),s[l]=!0;let u=null;if(l in i&&i[l].type===h)u=i[l];else{const f=Ce.getScale(h);u=new f({id:l,type:h,ctx:this.ctx,chart:this}),i[u.id]=u}u.init(a,t)}),ct(s,(r,a)=>{r||delete i[a]}),ct(i,r=>{Ft.configure(this,r,r.options),Ft.addBox(this,r)})}_updateMetasets(){const t=this._metasets,n=this.data.datasets.length,i=t.length;if(t.sort((s,o)=>s.index-o.index),i>n){for(let s=n;sn.length&&delete this._stacks,t.forEach((i,s)=>{n.filter(o=>o===i._dataset).length===0&&this._destroyDatasetMeta(s)})}buildOrUpdateControllers(){const t=[],n=this.data.datasets;let i,s;for(this._removeUnreferencedMetasets(),i=0,s=n.length;i{this.getDatasetMeta(n).controller.reset()},this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const n=this.config;n.update();const i=this._options=n.createResolver(n.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!i.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0})===!1)return;const o=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let r=0;for(let c=0,h=this.data.datasets.length;c{c.reset()}),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(wl("z","_idx"));const{_active:a,_lastEvent:l}=this;l?this._eventHandler(l,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){ct(this.scales,t=>{Ft.removeBox(this,t)}),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,n=new Set(Object.keys(this._listeners)),i=new Set(t.events);(!Ia(n,i)||!!this._responsiveListeners!==t.responsive)&&(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,n=this._getUniformDataChanges()||[];for(const{method:i,start:s,count:o}of n){const r=i==="_removeElements"?-o:o;e_(t,s,r)}}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const n=this.data.datasets.length,i=o=>new Set(t.filter(r=>r[0]===o).map((r,a)=>a+","+r.splice(1).join(","))),s=i(0);for(let o=1;oo.split(",")).map(o=>({method:o[1],start:+o[2],count:+o[3]}))}_updateLayout(t){if(this.notifyPlugins("beforeLayout",{cancelable:!0})===!1)return;Ft.update(this,this.width,this.height,t);const n=this.chartArea,i=n.width<=0||n.height<=0;this._layers=[],ct(this.boxes,s=>{i&&s.position==="chartArea"||(s.configure&&s.configure(),this._layers.push(...s._layers()))},this),this._layers.forEach((s,o)=>{s._idx=o}),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})!==!1){for(let n=0,i=this.data.datasets.length;n=0;--n)this._drawDataset(t[n]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const n=this.ctx,i={meta:t,index:t.index,cancelable:!0},s=Ah(this,t);this.notifyPlugins("beforeDatasetDraw",i)!==!1&&(s&&Lo(n,s),t.controller.draw(),s&&Ro(n),i.cancelable=!1,this.notifyPlugins("afterDatasetDraw",i))}isPointInArea(t){return qe(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,n,i,s){const o=Qm.modes[n];return typeof o=="function"?o(this,t,i,s):[]}getDatasetMeta(t){const n=this.data.datasets[t],i=this._metasets;let s=i.filter(o=>o&&o._dataset===n).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:n&&n.order||0,index:t,_dataset:n,_parsed:[],_sorted:!1},i.push(s)),s}getContext(){return this.$context||(this.$context=yn(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const n=this.data.datasets[t];if(!n)return!1;const i=this.getDatasetMeta(t);return typeof i.hidden=="boolean"?!i.hidden:!n.hidden}setDatasetVisibility(t,n){const i=this.getDatasetMeta(t);i.hidden=!n}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,n,i){const s=i?"show":"hide",o=this.getDatasetMeta(t),r=o.controller._resolveAnimations(void 0,s);is(n)?(o.data[n].hidden=!i,this.update()):(this.setDatasetVisibility(t,i),r.update(o,{visible:i}),this.update(a=>a.datasetIndex===t?s:void 0))}hide(t,n){this._updateVisibility(t,n,!1)}show(t,n){this._updateVisibility(t,n,!0)}_destroyDatasetMeta(t){const n=this._metasets[t];n&&n.controller&&n.controller._destroy(),delete this._metasets[t]}_stop(){let t,n;for(this.stop(),Ne.remove(this),t=0,n=this.data.datasets.length;t{n.addEventListener(this,o,r),t[o]=r},s=(o,r,a)=>{o.offsetX=r,o.offsetY=a,this._eventHandler(o)};ct(this.options.events,o=>i(o,s))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,n=this.platform,i=(l,c)=>{n.addEventListener(this,l,c),t[l]=c},s=(l,c)=>{t[l]&&(n.removeEventListener(this,l,c),delete t[l])},o=(l,c)=>{this.canvas&&this.resize(l,c)};let r;const a=()=>{s("attach",a),this.attached=!0,this.resize(),i("resize",o),i("detach",r)};r=()=>{this.attached=!1,s("resize",o),this._stop(),this._resize(0,0),i("attach",a)},n.isAttached(this.canvas)?a():r()}unbindEvents(){ct(this._listeners,(t,n)=>{this.platform.removeEventListener(this,n,t)}),this._listeners={},ct(this._responsiveListeners,(t,n)=>{this.platform.removeEventListener(this,n,t)}),this._responsiveListeners=void 0}updateHoverStyle(t,n,i){const s=i?"set":"remove";let o,r,a,l;for(n==="dataset"&&(o=this.getDatasetMeta(t[0].datasetIndex),o.controller["_"+s+"DatasetHoverStyle"]()),a=0,l=t.length;a{const a=this.getDatasetMeta(o);if(!a)throw new Error("No dataset found at index "+o);return{datasetIndex:o,element:a.data[r],index:r}});!mo(i,n)&&(this._active=i,this._lastEvent=null,this._updateHoverStyles(i,n))}notifyPlugins(t,n,i){return this._plugins.notify(this,t,n,i)}isPluginEnabled(t){return this._plugins._cache.filter(n=>n.plugin.id===t).length===1}_updateHoverStyles(t,n,i){const s=this.options.hover,o=(l,c)=>l.filter(h=>!c.some(u=>h.datasetIndex===u.datasetIndex&&h.index===u.index)),r=o(n,t),a=i?t:o(t,n);r.length&&this.updateHoverStyle(r,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(t,n){const i={event:t,replay:n,cancelable:!0,inChartArea:this.isPointInArea(t)},s=r=>(r.options.events||this.options.events).includes(t.native.type);if(this.notifyPlugins("beforeEvent",i,s)===!1)return;const o=this._handleEvent(t,n,i.inChartArea);return i.cancelable=!1,this.notifyPlugins("afterEvent",i,s),(o||i.changed)&&this.render(),this}_handleEvent(t,n,i){const{_active:s=[],options:o}=this,r=n,a=this._getActiveElements(t,s,i,r),l=cp(t),c=n_(t,this._lastEvent,i,l);i&&(this._lastEvent=null,ht(o.onHover,[t,a,this],this),l&&ht(o.onClick,[t,a,this],this));const h=!mo(a,s);return(h||n)&&(this._active=a,this._updateHoverStyles(a,s,n)),this._lastEvent=c,h}_getActiveElements(t,n,i,s){if(t.type==="mouseout")return[];if(!i)return n;const o=this.options.hover;return this.getElementsAtEventForMode(t,o.mode,o,s)}}D(Ve,"defaults",pt),D(Ve,"instances",lo),D(Ve,"overrides",Jn),D(Ve,"registry",Ce),D(Ve,"version",Zv),D(Ve,"getChart",Ml);function Sl(){return ct(Ve.instances,e=>e._plugins.invalidate())}function i_(e,t,n){const{startAngle:i,x:s,y:o,outerRadius:r,innerRadius:a,options:l}=t,{borderWidth:c,borderJoinStyle:h}=l,u=Math.min(c/r,It(i-n));if(e.beginPath(),e.arc(s,o,r-c/2,i+u/2,n-u/2),a>0){const f=Math.min(c/a,It(i-n));e.arc(s,o,a+c/2,n-f/2,i+f/2,!0)}else{const f=Math.min(c/2,r*It(i-n));if(h==="round")e.arc(s,o,f,n-st/2,i+st/2,!0);else if(h==="bevel"){const d=2*f*f,g=-d*Math.cos(n+st/2)+s,m=-d*Math.sin(n+st/2)+o,p=d*Math.cos(i+st/2)+s,v=d*Math.sin(i+st/2)+o;e.lineTo(g,m),e.lineTo(p,v)}}e.closePath(),e.moveTo(0,0),e.rect(0,0,e.canvas.width,e.canvas.height),e.clip("evenodd")}function s_(e,t,n){const{startAngle:i,pixelMargin:s,x:o,y:r,outerRadius:a,innerRadius:l}=t;let c=s/a;e.beginPath(),e.arc(o,r,a,i-c,n+c),l>s?(c=s/l,e.arc(o,r,l,n+c,i-c,!0)):e.arc(o,r,s,n+xt,i-xt),e.closePath(),e.clip()}function o_(e){return ca(e,["outerStart","outerEnd","innerStart","innerEnd"])}function r_(e,t,n,i){const s=o_(e.options.borderRadius),o=(n-t)/2,r=Math.min(o,i*t/2),a=l=>{const c=(n-Math.min(o,l))*i/2;return At(l,0,Math.min(o,c))};return{outerStart:a(s.outerStart),outerEnd:a(s.outerEnd),innerStart:At(s.innerStart,0,r),innerEnd:At(s.innerEnd,0,r)}}function ii(e,t,n,i){return{x:n+e*Math.cos(t),y:i+e*Math.sin(t)}}function wo(e,t,n,i,s,o){const{x:r,y:a,startAngle:l,pixelMargin:c,innerRadius:h}=t,u=Math.max(t.outerRadius+i+n-c,0),f=h>0?h+i+n+c:0;let d=0;const g=s-l;if(i){const z=h>0?h-i:0,H=u>0?u-i:0,K=(z+H)/2,et=K!==0?g*K/(K+i):g;d=(g-et)/2}const m=Math.max(.001,g*u-n/st)/u,p=(g-m)/2,v=l+p+d,_=s-p-d,{outerStart:x,outerEnd:y,innerStart:w,innerEnd:S}=r_(t,f,u,_-v),M=u-x,P=u-y,E=v+x/M,I=_-y/P,j=f+w,O=f+S,F=v+w/j,V=_-S/O;if(e.beginPath(),o){const z=(E+I)/2;if(e.arc(r,a,u,E,z),e.arc(r,a,u,z,I),y>0){const U=ii(P,I,r,a);e.arc(U.x,U.y,y,I,_+xt)}const H=ii(O,_,r,a);if(e.lineTo(H.x,H.y),S>0){const U=ii(O,V,r,a);e.arc(U.x,U.y,S,_+xt,V+Math.PI)}const K=(_-S/f+(v+w/f))/2;if(e.arc(r,a,f,_-S/f,K,!0),e.arc(r,a,f,K,v+w/f,!0),w>0){const U=ii(j,F,r,a);e.arc(U.x,U.y,w,F+Math.PI,v-xt)}const et=ii(M,v,r,a);if(e.lineTo(et.x,et.y),x>0){const U=ii(M,E,r,a);e.arc(U.x,U.y,x,v-xt,E)}}else{e.moveTo(r,a);const z=Math.cos(E)*u+r,H=Math.sin(E)*u+a;e.lineTo(z,H);const K=Math.cos(I)*u+r,et=Math.sin(I)*u+a;e.lineTo(K,et)}e.closePath()}function a_(e,t,n,i,s){const{fullCircles:o,startAngle:r,circumference:a}=t;let l=t.endAngle;if(o){wo(e,t,n,i,l,s);for(let c=0;c=st&&d===0&&h!=="miter"&&i_(e,t,m),o||(wo(e,t,n,i,m,s),e.stroke())}class ri extends tn{constructor(n){super();D(this,"circumference");D(this,"endAngle");D(this,"fullCircles");D(this,"innerRadius");D(this,"outerRadius");D(this,"pixelMargin");D(this,"startAngle");this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,n&&Object.assign(this,n)}inRange(n,i,s){const o=this.getProps(["x","y"],s),{angle:r,distance:a}=ah(o,{x:n,y:i}),{startAngle:l,endAngle:c,innerRadius:h,outerRadius:u,circumference:f}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],s),d=(this.options.spacing+this.options.borderWidth)/2,g=W(f,c-l),m=ss(r,l,c)&&l!==c,p=g>=gt||m,v=Ue(a,h+d,u+d);return p&&v}getCenterPoint(n){const{x:i,y:s,startAngle:o,endAngle:r,innerRadius:a,outerRadius:l}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],n),{offset:c,spacing:h}=this.options,u=(o+r)/2,f=(a+l+h+c)/2;return{x:i+Math.cos(u)*f,y:s+Math.sin(u)*f}}tooltipPosition(n){return this.getCenterPoint(n)}draw(n){const{options:i,circumference:s}=this,o=(i.offset||0)/4,r=(i.spacing||0)/2,a=i.circular;if(this.pixelMargin=i.borderAlign==="inner"?.33:0,this.fullCircles=s>gt?Math.floor(s/gt):0,s===0||this.innerRadius<0||this.outerRadius<0)return;n.save();const l=(this.startAngle+this.endAngle)/2;n.translate(Math.cos(l)*o,Math.sin(l)*o);const c=1-Math.sin(Math.min(st,s||0)),h=o*c;n.fillStyle=i.backgroundColor,n.strokeStyle=i.borderColor,a_(n,this,h,r,a),l_(n,this,h,r,a),n.restore()}}D(ri,"id","arc"),D(ri,"defaults",{borderAlign:"center",borderColor:"#fff",borderDash:[],borderDashOffset:0,borderJoinStyle:void 0,borderRadius:0,borderWidth:2,offset:0,spacing:0,angle:void 0,circular:!0,selfJoin:!1}),D(ri,"defaultRoutes",{backgroundColor:"backgroundColor"}),D(ri,"descriptors",{_scriptable:!0,_indexable:n=>n!=="borderDash"});function Vh(e,t,n=t){e.lineCap=W(n.borderCapStyle,t.borderCapStyle),e.setLineDash(W(n.borderDash,t.borderDash)),e.lineDashOffset=W(n.borderDashOffset,t.borderDashOffset),e.lineJoin=W(n.borderJoinStyle,t.borderJoinStyle),e.lineWidth=W(n.borderWidth,t.borderWidth),e.strokeStyle=W(n.borderColor,t.borderColor)}function c_(e,t,n){e.lineTo(n.x,n.y)}function h_(e){return e.stepped?Ep:e.tension||e.cubicInterpolationMode==="monotone"?Lp:c_}function Wh(e,t,n={}){const i=e.length,{start:s=0,end:o=i-1}=n,{start:r,end:a}=t,l=Math.max(s,r),c=Math.min(o,a),h=sa&&o>a;return{count:i,start:l,loop:t.loop,ilen:c(r+(c?a-y:y))%o,x=()=>{m!==p&&(e.lineTo(h,p),e.lineTo(h,m),e.lineTo(h,v))};for(l&&(d=s[_(0)],e.moveTo(d.x,d.y)),f=0;f<=a;++f){if(d=s[_(f)],d.skip)continue;const y=d.x,w=d.y,S=y|0;S===g?(wp&&(p=w),h=(u*h+y)/++u):(x(),e.lineTo(y,w),g=S,u=0,m=p=w),v=w}x()}function Er(e){const t=e.options,n=t.borderDash&&t.borderDash.length;return!e._decimated&&!e._loop&&!t.tension&&t.cubicInterpolationMode!=="monotone"&&!t.stepped&&!n?f_:u_}function d_(e){return e.stepped?um:e.tension||e.cubicInterpolationMode==="monotone"?fm:An}function g_(e,t,n,i){let s=t._path;s||(s=t._path=new Path2D,t.path(s,n,i)&&s.closePath()),Vh(e,t.options),e.stroke(s)}function p_(e,t,n,i){const{segments:s,options:o}=t,r=Er(t);for(const a of s)Vh(e,o,a.style),e.beginPath(),r(e,t,a,{start:n,end:n+i-1})&&e.closePath(),e.stroke()}const m_=typeof Path2D=="function";function v_(e,t,n,i){m_&&!t.options.segment?g_(e,t,n,i):p_(e,t,n,i)}class fn extends tn{constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,n){const i=this.options;if((i.tension||i.cubicInterpolationMode==="monotone")&&!i.stepped&&!this._pointsUpdated){const s=i.spanGaps?this._loop:this._fullLoop;im(this._points,i,t,s,n),this._pointsUpdated=!0}}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=_m(this,this.options.segment))}first(){const t=this.segments,n=this.points;return t.length&&n[t[0].start]}last(){const t=this.segments,n=this.points,i=t.length;return i&&n[t[i-1].end]}interpolate(t,n){const i=this.options,s=t[n],o=this.points,r=Ch(this,{property:n,start:s,end:s});if(!r.length)return;const a=[],l=d_(i);let c,h;for(c=0,h=r.length;ct!=="borderDash"&&t!=="fill"});function Pl(e,t,n,i){const s=e.options,{[n]:o}=e.getProps([n],i);return Math.abs(t-o)e.replace("rgb(","rgba(").replace(")",", 0.5)"));function Yh(e){return Lr[e%Lr.length]}function Uh(e){return Cl[e%Cl.length]}function M_(e,t){return e.borderColor=Yh(t),e.backgroundColor=Uh(t),++t}function S_(e,t){return e.backgroundColor=e.data.map(()=>Yh(t++)),t}function P_(e,t){return e.backgroundColor=e.data.map(()=>Uh(t++)),t}function C_(e){let t=0;return(n,i)=>{const s=e.getDatasetMeta(i).controller;s instanceof En?t=S_(n,t):s instanceof Ki?t=P_(n,t):s&&(t=M_(n,t))}}function Al(e){let t;for(t in e)if(e[t].borderColor||e[t].backgroundColor)return!0;return!1}function A_(e){return e&&(e.borderColor||e.backgroundColor)}function D_(){return pt.borderColor!=="rgba(0,0,0,0.1)"||pt.backgroundColor!=="rgba(0,0,0,0.1)"}var T_={id:"colors",defaults:{enabled:!0,forceOverride:!1},beforeLayout(e,t,n){if(!n.enabled)return;const{data:{datasets:i},options:s}=e.config,{elements:o}=s,r=Al(i)||A_(s)||o&&Al(o)||D_();if(!n.forceOverride&&r)return;const a=C_(e);i.forEach(a)}};function O_(e,t,n,i,s){const o=s.samples||i;if(o>=n)return e.slice(t,t+n);const r=[],a=(n-2)/(o-2);let l=0;const c=t+n-1;let h=t,u,f,d,g,m;for(r[l++]=e[h],u=0;ud&&(d=g,f=e[_],m=_);r[l++]=f,h=m}return r[l++]=e[c],r}function E_(e,t,n,i){let s=0,o=0,r,a,l,c,h,u,f,d,g,m;const p=[],v=t+n-1,_=e[t].x,y=e[v].x-_;for(r=t;rm&&(m=c,f=r),s=(o*s+a.x)/++o;else{const S=r-1;if(!$(u)&&!$(f)){const M=Math.min(u,f),P=Math.max(u,f);M!==d&&M!==S&&p.push({...e[M],x:s}),P!==d&&P!==S&&p.push({...e[P],x:s})}r>0&&S!==d&&p.push(e[S]),p.push(a),h=w,o=0,g=m=c,u=f=d=r}}return p}function Xh(e){if(e._decimated){const t=e._data;delete e._decimated,delete e._data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,writable:!0,value:t})}}function Dl(e){e.data.datasets.forEach(t=>{Xh(t)})}function L_(e,t){const n=t.length;let i=0,s;const{iScale:o}=e,{min:r,max:a,minDefined:l,maxDefined:c}=o.getUserBounds();return l&&(i=At(Xe(t,o.axis,r).lo,0,n-1)),c?s=At(Xe(t,o.axis,a).hi+1,i,n)-i:s=n-i,{start:i,count:s}}var R_={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(e,t,n)=>{if(!n.enabled){Dl(e);return}const i=e.width;e.data.datasets.forEach((s,o)=>{const{_data:r,indexAxis:a}=s,l=e.getDatasetMeta(o),c=r||s.data;if(ft([a,e.options.indexAxis])==="y"||!l.controller.supportsDecimation)return;const h=e.scales[l.xAxisID];if(h.type!=="linear"&&h.type!=="time"||e.options.parsing)return;let{start:u,count:f}=L_(l,c);const d=n.threshold||4*i;if(f<=d){Xh(s);return}$(r)&&(s._data=c,delete s.data,Object.defineProperty(s,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(m){this._data=m}}));let g;switch(n.algorithm){case"lttb":g=O_(c,u,f,i,n);break;case"min-max":g=E_(c,u,f,i);break;default:throw new Error(`Unsupported decimation algorithm '${n.algorithm}'`)}s._decimated=g})},destroy(e){Dl(e)}};function I_(e,t,n){const i=e.segments,s=e.points,o=t.points,r=[];for(const a of i){let{start:l,end:c}=a;c=zo(l,c,s);const h=Rr(n,s[l],s[c],a.loop);if(!t.segments){r.push({source:a,target:h,start:s[l],end:s[c]});continue}const u=Ch(t,h);for(const f of u){const d=Rr(n,o[f.start],o[f.end],f.loop),g=Ph(a,s,d);for(const m of g)r.push({source:m,target:f,start:{[n]:Tl(h,d,"start",Math.max)},end:{[n]:Tl(h,d,"end",Math.min)}})}}return r}function Rr(e,t,n,i){if(i)return;let s=t[e],o=n[e];return e==="angle"&&(s=It(s),o=It(o)),{property:e,start:s,end:o}}function F_(e,t){const{x:n=null,y:i=null}=e||{},s=t.points,o=[];return t.segments.forEach(({start:r,end:a})=>{a=zo(r,a,s);const l=s[r],c=s[a];i!==null?(o.push({x:l.x,y:i}),o.push({x:c.x,y:i})):n!==null&&(o.push({x:n,y:l.y}),o.push({x:n,y:c.y}))}),o}function zo(e,t,n){for(;t>e;t--){const i=n[t];if(!isNaN(i.x)&&!isNaN(i.y))break}return t}function Tl(e,t,n,i){return e&&t?i(e[n],t[n]):e?e[n]:t?t[n]:0}function qh(e,t){let n=[],i=!1;return dt(e)?(i=!0,n=e):n=F_(e,t),n.length?new fn({points:n,options:{tension:0},_loop:i,_fullLoop:i}):null}function Ol(e){return e&&e.fill!==!1}function z_(e,t,n){let s=e[t].fill;const o=[t];let r;if(!n)return s;for(;s!==!1&&o.indexOf(s)===-1;){if(!vt(s))return s;if(r=e[s],!r)return!1;if(r.visible)return s;o.push(s),s=r.fill}return!1}function N_(e,t,n){const i=W_(e);if(Z(i))return isNaN(i.value)?!1:i;let s=parseFloat(i);return vt(s)&&Math.floor(s)===s?B_(i[0],t,s,n):["origin","start","end","stack","shape"].indexOf(i)>=0&&i}function B_(e,t,n,i){return(e==="-"||e==="+")&&(n=t+n),n===t||n<0||n>=i?!1:n}function j_(e,t){let n=null;return e==="start"?n=t.bottom:e==="end"?n=t.top:Z(e)?n=t.getPixelForValue(e.value):t.getBasePixel&&(n=t.getBasePixel()),n}function V_(e,t,n){let i;return e==="start"?i=n:e==="end"?i=t.options.reverse?t.min:t.max:Z(e)?i=e.value:i=t.getBaseValue(),i}function W_(e){const t=e.options,n=t.fill;let i=W(n&&n.target,n);return i===void 0&&(i=!!t.backgroundColor),i===!1||i===null?!1:i===!0?"origin":i}function H_(e){const{scale:t,index:n,line:i}=e,s=[],o=i.segments,r=i.points,a=Y_(t,n);a.push(qh({x:null,y:t.bottom},i));for(let l=0;l=0;--r){const a=s[r].$filler;a&&(a.line.updateControlPoints(o,a.axis),i&&a.fill&&ar(e.ctx,a,o))}},beforeDatasetsDraw(e,t,n){if(n.drawTime!=="beforeDatasetsDraw")return;const i=e.getSortedVisibleDatasetMetas();for(let s=i.length-1;s>=0;--s){const o=i[s].$filler;Ol(o)&&ar(e.ctx,o,e.chartArea)}},beforeDatasetDraw(e,t,n){const i=t.meta.$filler;!Ol(i)||n.drawTime!=="beforeDatasetDraw"||ar(e.ctx,i,e.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const Il=(e,t)=>{let{boxHeight:n=t,boxWidth:i=t}=e;return e.usePointStyle&&(n=Math.min(n,t),i=e.pointStyleWidth||Math.min(i,t)),{boxWidth:i,boxHeight:n,itemHeight:Math.max(t,n)}},eb=(e,t)=>e!==null&&t!==null&&e.datasetIndex===t.datasetIndex&&e.index===t.index;class Fl extends tn{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,n,i){this.maxWidth=t,this.maxHeight=n,this._margins=i,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let n=ht(t.generateLabels,[this.chart],this)||[];t.filter&&(n=n.filter(i=>t.filter(i,this.chart.data))),t.sort&&(n=n.sort((i,s)=>t.sort(i,s,this.chart.data))),this.options.reverse&&n.reverse(),this.legendItems=n}fit(){const{options:t,ctx:n}=this;if(!t.display){this.width=this.height=0;return}const i=t.labels,s=wt(i.font),o=s.size,r=this._computeTitleHeight(),{boxWidth:a,itemHeight:l}=Il(i,o);let c,h;n.font=s.string,this.isHorizontal()?(c=this.maxWidth,h=this._fitRows(r,o,a,l)+10):(h=this.maxHeight,c=this._fitCols(r,s,a,l)+10),this.width=Math.min(c,t.maxWidth||this.maxWidth),this.height=Math.min(h,t.maxHeight||this.maxHeight)}_fitRows(t,n,i,s){const{ctx:o,maxWidth:r,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.lineWidths=[0],h=s+a;let u=t;o.textAlign="left",o.textBaseline="middle";let f=-1,d=-h;return this.legendItems.forEach((g,m)=>{const p=i+n/2+o.measureText(g.text).width;(m===0||c[c.length-1]+p+2*a>r)&&(u+=h,c[c.length-(m>0?0:1)]=0,d+=h,f++),l[m]={left:0,top:d,row:f,width:p,height:s},c[c.length-1]+=p+a}),u}_fitCols(t,n,i,s){const{ctx:o,maxHeight:r,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.columnSizes=[],h=r-t;let u=a,f=0,d=0,g=0,m=0;return this.legendItems.forEach((p,v)=>{const{itemWidth:_,itemHeight:x}=nb(i,n,o,p,s);v>0&&d+x+2*a>h&&(u+=f+a,c.push({width:f,height:d}),g+=f+a,m++,f=d=0),l[v]={left:g,top:d,col:m,width:_,height:x},f=Math.max(f,_),d+=x+a}),u+=f,c.push({width:f,height:d}),u}adjustHitBoxes(){if(!this.options.display)return;const t=this._computeTitleHeight(),{legendHitBoxes:n,options:{align:i,labels:{padding:s},rtl:o}}=this,r=li(o,this.left,this.width);if(this.isHorizontal()){let a=0,l=Rt(i,this.left+s,this.right-this.lineWidths[a]);for(const c of n)a!==c.row&&(a=c.row,l=Rt(i,this.left+s,this.right-this.lineWidths[a])),c.top+=this.top+t+s,c.left=r.leftForLtr(r.x(l),c.width),l+=c.width+s}else{let a=0,l=Rt(i,this.top+t+s,this.bottom-this.columnSizes[a].height);for(const c of n)c.col!==a&&(a=c.col,l=Rt(i,this.top+t+s,this.bottom-this.columnSizes[a].height)),c.top=l,c.left+=this.left+s,c.left=r.leftForLtr(r.x(c.left),c.width),l+=c.height+s}}isHorizontal(){return this.options.position==="top"||this.options.position==="bottom"}draw(){if(this.options.display){const t=this.ctx;Lo(t,this),this._draw(),Ro(t)}}_draw(){const{options:t,columnSizes:n,lineWidths:i,ctx:s}=this,{align:o,labels:r}=t,a=pt.color,l=li(t.rtl,this.left,this.width),c=wt(r.font),{padding:h}=r,u=c.size,f=u/2;let d;this.drawTitle(),s.textAlign=l.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=c.string;const{boxWidth:g,boxHeight:m,itemHeight:p}=Il(r,u),v=function(S,M,P){if(isNaN(g)||g<=0||isNaN(m)||m<0)return;s.save();const E=W(P.lineWidth,1);if(s.fillStyle=W(P.fillStyle,a),s.lineCap=W(P.lineCap,"butt"),s.lineDashOffset=W(P.lineDashOffset,0),s.lineJoin=W(P.lineJoin,"miter"),s.lineWidth=E,s.strokeStyle=W(P.strokeStyle,a),s.setLineDash(W(P.lineDash,[])),r.usePointStyle){const I={radius:m*Math.SQRT2/2,pointStyle:P.pointStyle,rotation:P.rotation,borderWidth:E},j=l.xPlus(S,g/2),O=M+f;ph(s,I,j,O,r.pointStyleWidth&&g)}else{const I=M+Math.max((u-m)/2,0),j=l.leftForLtr(S,g),O=Un(P.borderRadius);s.beginPath(),Object.values(O).some(F=>F!==0)?os(s,{x:j,y:I,w:g,h:m,radius:O}):s.rect(j,I,g,m),s.fill(),E!==0&&s.stroke()}s.restore()},_=function(S,M,P){Zn(s,P.text,S,M+p/2,c,{strikethrough:P.hidden,textAlign:l.textAlign(P.textAlign)})},x=this.isHorizontal(),y=this._computeTitleHeight();x?d={x:Rt(o,this.left+h,this.right-i[0]),y:this.top+h+y,line:0}:d={x:this.left+h,y:Rt(o,this.top+y+h,this.bottom-n[0].height),line:0},kh(this.ctx,t.textDirection);const w=p+h;this.legendItems.forEach((S,M)=>{s.strokeStyle=S.fontColor,s.fillStyle=S.fontColor;const P=s.measureText(S.text).width,E=l.textAlign(S.textAlign||(S.textAlign=r.textAlign)),I=g+f+P;let j=d.x,O=d.y;l.setWidth(this.width),x?M>0&&j+I+h>this.right&&(O=d.y+=w,d.line++,j=d.x=Rt(o,this.left+h,this.right-i[d.line])):M>0&&O+w>this.bottom&&(j=d.x=j+n[d.line].width+h,d.line++,O=d.y=Rt(o,this.top+y+h,this.bottom-n[d.line].height));const F=l.x(j);if(v(F,O,S),j=yp(E,j+g+f,x?j+I:this.right,t.rtl),_(l.x(j),O,S),x)d.x+=I+h;else if(typeof S.text!="string"){const V=c.lineHeight;d.y+=Kh(S,V)+h}else d.y+=w}),Mh(this.ctx,t.textDirection)}drawTitle(){const t=this.options,n=t.title,i=wt(n.font),s=Et(n.padding);if(!n.display)return;const o=li(t.rtl,this.left,this.width),r=this.ctx,a=n.position,l=i.size/2,c=s.top+l;let h,u=this.left,f=this.width;if(this.isHorizontal())f=Math.max(...this.lineWidths),h=this.top+c,u=Rt(t.align,u,this.right-f);else{const g=this.columnSizes.reduce((m,p)=>Math.max(m,p.height),0);h=c+Rt(t.align,this.top,this.bottom-g-t.labels.padding-this._computeTitleHeight())}const d=Rt(a,u,u+f);r.textAlign=o.textAlign(aa(a)),r.textBaseline="middle",r.strokeStyle=n.color,r.fillStyle=n.color,r.font=i.string,Zn(r,n.text,d,h,i)}_computeTitleHeight(){const t=this.options.title,n=wt(t.font),i=Et(t.padding);return t.display?n.lineHeight+i.height:0}_getLegendItemAt(t,n){let i,s,o;if(Ue(t,this.left,this.right)&&Ue(n,this.top,this.bottom)){for(o=this.legendHitBoxes,i=0;io.length>r.length?o:r)),t+n.size/2+i.measureText(s).width}function sb(e,t,n){let i=e;return typeof t.text!="string"&&(i=Kh(t,n)),i}function Kh(e,t){const n=e.text?e.text.length:0;return t*n}function ob(e,t){return!!((e==="mousemove"||e==="mouseout")&&(t.onHover||t.onLeave)||t.onClick&&(e==="click"||e==="mouseup"))}var rb={id:"legend",_element:Fl,start(e,t,n){const i=e.legend=new Fl({ctx:e.ctx,options:n,chart:e});Ft.configure(e,i,n),Ft.addBox(e,i)},stop(e){Ft.removeBox(e,e.legend),delete e.legend},beforeUpdate(e,t,n){const i=e.legend;Ft.configure(e,i,n),i.options=n},afterUpdate(e){const t=e.legend;t.buildLabels(),t.adjustHitBoxes()},afterEvent(e,t){t.replay||e.legend.handleEvent(t.event)},defaults:{display:!0,position:"top",align:"center",fullSize:!0,reverse:!1,weight:1e3,onClick(e,t,n){const i=t.datasetIndex,s=n.chart;s.isDatasetVisible(i)?(s.hide(i),t.hidden=!0):(s.show(i),t.hidden=!1)},onHover:null,onLeave:null,labels:{color:e=>e.chart.options.color,boxWidth:40,padding:10,generateLabels(e){const t=e.data.datasets,{labels:{usePointStyle:n,pointStyle:i,textAlign:s,color:o,useBorderRadius:r,borderRadius:a}}=e.legend.options;return e._getSortedDatasetMetas().map(l=>{const c=l.controller.getStyle(n?0:void 0),h=Et(c.borderWidth);return{text:t[l.index].label,fillStyle:c.backgroundColor,fontColor:o,hidden:!l.visible,lineCap:c.borderCapStyle,lineDash:c.borderDash,lineDashOffset:c.borderDashOffset,lineJoin:c.borderJoinStyle,lineWidth:(h.width+h.height)/4,strokeStyle:c.borderColor,pointStyle:i||c.pointStyle,rotation:c.rotation,textAlign:s||c.textAlign,borderRadius:r&&(a||c.borderRadius),datasetIndex:l.index}},this)}},title:{color:e=>e.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:e=>!e.startsWith("on"),labels:{_scriptable:e=>!["generateLabels","filter","sort"].includes(e)}}};class ma extends tn{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,n){const i=this.options;if(this.left=0,this.top=0,!i.display){this.width=this.height=this.right=this.bottom=0;return}this.width=this.right=t,this.height=this.bottom=n;const s=dt(i.text)?i.text.length:1;this._padding=Et(i.padding);const o=s*wt(i.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const t=this.options.position;return t==="top"||t==="bottom"}_drawArgs(t){const{top:n,left:i,bottom:s,right:o,options:r}=this,a=r.align;let l=0,c,h,u;return this.isHorizontal()?(h=Rt(a,i,o),u=n+t,c=o-i):(r.position==="left"?(h=i+t,u=Rt(a,s,n),l=st*-.5):(h=o-t,u=Rt(a,n,s),l=st*.5),c=s-n),{titleX:h,titleY:u,maxWidth:c,rotation:l}}draw(){const t=this.ctx,n=this.options;if(!n.display)return;const i=wt(n.font),o=i.lineHeight/2+this._padding.top,{titleX:r,titleY:a,maxWidth:l,rotation:c}=this._drawArgs(o);Zn(t,n.text,0,0,i,{color:n.color,maxWidth:l,rotation:c,textAlign:aa(n.align),textBaseline:"middle",translation:[r,a]})}}function ab(e,t){const n=new ma({ctx:e.ctx,options:t,chart:e});Ft.configure(e,n,t),Ft.addBox(e,n),e.titleBlock=n}var lb={id:"title",_element:ma,start(e,t,n){ab(e,n)},stop(e){const t=e.titleBlock;Ft.removeBox(e,t),delete e.titleBlock},beforeUpdate(e,t,n){const i=e.titleBlock;Ft.configure(e,i,n),i.options=n},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Ns=new WeakMap;var cb={id:"subtitle",start(e,t,n){const i=new ma({ctx:e.ctx,options:n,chart:e});Ft.configure(e,i,n),Ft.addBox(e,i),Ns.set(e,i)},stop(e){Ft.removeBox(e,Ns.get(e)),Ns.delete(e)},beforeUpdate(e,t,n){const i=Ns.get(e);Ft.configure(e,i,n),i.options=n},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Bi={average(e){if(!e.length)return!1;let t,n,i=new Set,s=0,o=0;for(t=0,n=e.length;ta+l)/i.size,y:s/o}},nearest(e,t){if(!e.length)return!1;let n=t.x,i=t.y,s=Number.POSITIVE_INFINITY,o,r,a;for(o=0,r=e.length;o-1?e.split(` -`):e}function hb(e,t){const{element:n,datasetIndex:i,index:s}=t,o=e.getDatasetMeta(i).controller,{label:r,value:a}=o.getLabelAndValue(s);return{chart:e,label:r,parsed:o.getParsed(s),raw:e.data.datasets[i].data[s],formattedValue:a,dataset:o.getDataset(),dataIndex:s,datasetIndex:i,element:n}}function zl(e,t){const n=e.chart.ctx,{body:i,footer:s,title:o}=e,{boxWidth:r,boxHeight:a}=t,l=wt(t.bodyFont),c=wt(t.titleFont),h=wt(t.footerFont),u=o.length,f=s.length,d=i.length,g=Et(t.padding);let m=g.height,p=0,v=i.reduce((y,w)=>y+w.before.length+w.lines.length+w.after.length,0);if(v+=e.beforeBody.length+e.afterBody.length,u&&(m+=u*c.lineHeight+(u-1)*t.titleSpacing+t.titleMarginBottom),v){const y=t.displayColors?Math.max(a,l.lineHeight):l.lineHeight;m+=d*y+(v-d)*l.lineHeight+(v-1)*t.bodySpacing}f&&(m+=t.footerMarginTop+f*h.lineHeight+(f-1)*t.footerSpacing);let _=0;const x=function(y){p=Math.max(p,n.measureText(y).width+_)};return n.save(),n.font=c.string,ct(e.title,x),n.font=l.string,ct(e.beforeBody.concat(e.afterBody),x),_=t.displayColors?r+2+t.boxPadding:0,ct(i,y=>{ct(y.before,x),ct(y.lines,x),ct(y.after,x)}),_=0,n.font=h.string,ct(e.footer,x),n.restore(),p+=g.width,{width:p,height:m}}function ub(e,t){const{y:n,height:i}=t;return ne.height-i/2?"bottom":"center"}function fb(e,t,n,i){const{x:s,width:o}=i,r=n.caretSize+n.caretPadding;if(e==="left"&&s+o+r>t.width||e==="right"&&s-o-r<0)return!0}function db(e,t,n,i){const{x:s,width:o}=n,{width:r,chartArea:{left:a,right:l}}=e;let c="center";return i==="center"?c=s<=(a+l)/2?"left":"right":s<=o/2?c="left":s>=r-o/2&&(c="right"),fb(c,e,t,n)&&(c="center"),c}function Nl(e,t,n){const i=n.yAlign||t.yAlign||ub(e,n);return{xAlign:n.xAlign||t.xAlign||db(e,t,n,i),yAlign:i}}function gb(e,t){let{x:n,width:i}=e;return t==="right"?n-=i:t==="center"&&(n-=i/2),n}function pb(e,t,n){let{y:i,height:s}=e;return t==="top"?i+=n:t==="bottom"?i-=s+n:i-=s/2,i}function Bl(e,t,n,i){const{caretSize:s,caretPadding:o,cornerRadius:r}=e,{xAlign:a,yAlign:l}=n,c=s+o,{topLeft:h,topRight:u,bottomLeft:f,bottomRight:d}=Un(r);let g=gb(t,a);const m=pb(t,l,c);return l==="center"?a==="left"?g+=c:a==="right"&&(g-=c):a==="left"?g-=Math.max(h,f)+s:a==="right"&&(g+=Math.max(u,d)+s),{x:At(g,0,i.width-t.width),y:At(m,0,i.height-t.height)}}function Bs(e,t,n){const i=Et(n.padding);return t==="center"?e.x+e.width/2:t==="right"?e.x+e.width-i.right:e.x+i.left}function jl(e){return Pe([],Be(e))}function mb(e,t,n){return yn(e,{tooltip:t,tooltipItems:n,type:"tooltip"})}function Vl(e,t){const n=t&&t.dataset&&t.dataset.tooltip&&t.dataset.tooltip.callbacks;return n?e.override(n):e}const Gh={beforeTitle:ze,title(e){if(e.length>0){const t=e[0],n=t.chart.data.labels,i=n?n.length:0;if(this&&this.options&&this.options.mode==="dataset")return t.dataset.label||"";if(t.label)return t.label;if(i>0&&t.dataIndex"u"?Gh[t].call(n,i):s}class Ir extends tn{constructor(t){super(),this.opacity=0,this._active=[],this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.chart=t.chart,this.options=t.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(t){this.options=t,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){const t=this._cachedAnimations;if(t)return t;const n=this.chart,i=this.options.setContext(this.getContext()),s=i.enabled&&n.options.animation&&i.animations,o=new Dh(this.chart,s);return s._cacheable&&(this._cachedAnimations=Object.freeze(o)),o}getContext(){return this.$context||(this.$context=mb(this.chart.getContext(),this,this._tooltipItems))}getTitle(t,n){const{callbacks:i}=n,s=jt(i,"beforeTitle",this,t),o=jt(i,"title",this,t),r=jt(i,"afterTitle",this,t);let a=[];return a=Pe(a,Be(s)),a=Pe(a,Be(o)),a=Pe(a,Be(r)),a}getBeforeBody(t,n){return jl(jt(n.callbacks,"beforeBody",this,t))}getBody(t,n){const{callbacks:i}=n,s=[];return ct(t,o=>{const r={before:[],lines:[],after:[]},a=Vl(i,o);Pe(r.before,Be(jt(a,"beforeLabel",this,o))),Pe(r.lines,jt(a,"label",this,o)),Pe(r.after,Be(jt(a,"afterLabel",this,o))),s.push(r)}),s}getAfterBody(t,n){return jl(jt(n.callbacks,"afterBody",this,t))}getFooter(t,n){const{callbacks:i}=n,s=jt(i,"beforeFooter",this,t),o=jt(i,"footer",this,t),r=jt(i,"afterFooter",this,t);let a=[];return a=Pe(a,Be(s)),a=Pe(a,Be(o)),a=Pe(a,Be(r)),a}_createItems(t){const n=this._active,i=this.chart.data,s=[],o=[],r=[];let a=[],l,c;for(l=0,c=n.length;lt.filter(h,u,f,i))),t.itemSort&&(a=a.sort((h,u)=>t.itemSort(h,u,i))),ct(a,h=>{const u=Vl(t.callbacks,h);s.push(jt(u,"labelColor",this,h)),o.push(jt(u,"labelPointStyle",this,h)),r.push(jt(u,"labelTextColor",this,h))}),this.labelColors=s,this.labelPointStyles=o,this.labelTextColors=r,this.dataPoints=a,a}update(t,n){const i=this.options.setContext(this.getContext()),s=this._active;let o,r=[];if(!s.length)this.opacity!==0&&(o={opacity:0});else{const a=Bi[i.position].call(this,s,this._eventPosition);r=this._createItems(i),this.title=this.getTitle(r,i),this.beforeBody=this.getBeforeBody(r,i),this.body=this.getBody(r,i),this.afterBody=this.getAfterBody(r,i),this.footer=this.getFooter(r,i);const l=this._size=zl(this,i),c=Object.assign({},a,l),h=Nl(this.chart,i,c),u=Bl(i,c,h,this.chart);this.xAlign=h.xAlign,this.yAlign=h.yAlign,o={opacity:1,x:u.x,y:u.y,width:l.width,height:l.height,caretX:a.x,caretY:a.y}}this._tooltipItems=r,this.$context=void 0,o&&this._resolveAnimations().update(this,o),t&&i.external&&i.external.call(this,{chart:this.chart,tooltip:this,replay:n})}drawCaret(t,n,i,s){const o=this.getCaretPosition(t,i,s);n.lineTo(o.x1,o.y1),n.lineTo(o.x2,o.y2),n.lineTo(o.x3,o.y3)}getCaretPosition(t,n,i){const{xAlign:s,yAlign:o}=this,{caretSize:r,cornerRadius:a}=i,{topLeft:l,topRight:c,bottomLeft:h,bottomRight:u}=Un(a),{x:f,y:d}=t,{width:g,height:m}=n;let p,v,_,x,y,w;return o==="center"?(y=d+m/2,s==="left"?(p=f,v=p-r,x=y+r,w=y-r):(p=f+g,v=p+r,x=y-r,w=y+r),_=p):(s==="left"?v=f+Math.max(l,h)+r:s==="right"?v=f+g-Math.max(c,u)-r:v=this.caretX,o==="top"?(x=d,y=x-r,p=v-r,_=v+r):(x=d+m,y=x+r,p=v+r,_=v-r),w=x),{x1:p,x2:v,x3:_,y1:x,y2:y,y3:w}}drawTitle(t,n,i){const s=this.title,o=s.length;let r,a,l;if(o){const c=li(i.rtl,this.x,this.width);for(t.x=Bs(this,i.titleAlign,i),n.textAlign=c.textAlign(i.titleAlign),n.textBaseline="middle",r=wt(i.titleFont),a=i.titleSpacing,n.fillStyle=i.titleColor,n.font=r.string,l=0;l_!==0)?(t.beginPath(),t.fillStyle=o.multiKeyBackground,os(t,{x:m,y:g,w:c,h:l,radius:v}),t.fill(),t.stroke(),t.fillStyle=r.backgroundColor,t.beginPath(),os(t,{x:p,y:g+1,w:c-2,h:l-2,radius:v}),t.fill()):(t.fillStyle=o.multiKeyBackground,t.fillRect(m,g,c,l),t.strokeRect(m,g,c,l),t.fillStyle=r.backgroundColor,t.fillRect(p,g+1,c-2,l-2))}t.fillStyle=this.labelTextColors[i]}drawBody(t,n,i){const{body:s}=this,{bodySpacing:o,bodyAlign:r,displayColors:a,boxHeight:l,boxWidth:c,boxPadding:h}=i,u=wt(i.bodyFont);let f=u.lineHeight,d=0;const g=li(i.rtl,this.x,this.width),m=function(P){n.fillText(P,g.x(t.x+d),t.y+f/2),t.y+=f+o},p=g.textAlign(r);let v,_,x,y,w,S,M;for(n.textAlign=r,n.textBaseline="middle",n.font=u.string,t.x=Bs(this,p,i),n.fillStyle=i.bodyColor,ct(this.beforeBody,m),d=a&&p!=="right"?r==="center"?c/2+h:c+2+h:0,y=0,S=s.length;y0&&n.stroke()}_updateAnimationTarget(t){const n=this.chart,i=this.$animations,s=i&&i.x,o=i&&i.y;if(s||o){const r=Bi[t.position].call(this,this._active,this._eventPosition);if(!r)return;const a=this._size=zl(this,t),l=Object.assign({},r,this._size),c=Nl(n,t,l),h=Bl(t,l,c,n);(s._to!==h.x||o._to!==h.y)&&(this.xAlign=c.xAlign,this.yAlign=c.yAlign,this.width=a.width,this.height=a.height,this.caretX=r.x,this.caretY=r.y,this._resolveAnimations().update(this,h))}}_willRender(){return!!this.opacity}draw(t){const n=this.options.setContext(this.getContext());let i=this.opacity;if(!i)return;this._updateAnimationTarget(n);const s={width:this.width,height:this.height},o={x:this.x,y:this.y};i=Math.abs(i)<.001?0:i;const r=Et(n.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;n.enabled&&a&&(t.save(),t.globalAlpha=i,this.drawBackground(o,t,s,n),kh(t,n.textDirection),o.y+=r.top,this.drawTitle(o,t,n),this.drawBody(o,t,n),this.drawFooter(o,t,n),Mh(t,n.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,n){const i=this._active,s=t.map(({datasetIndex:a,index:l})=>{const c=this.chart.getDatasetMeta(a);if(!c)throw new Error("Cannot find a dataset at index "+a);return{datasetIndex:a,element:c.data[l],index:l}}),o=!mo(i,s),r=this._positionChanged(s,n);(o||r)&&(this._active=s,this._eventPosition=n,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,n,i=!0){if(n&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,o=this._active||[],r=this._getActiveElements(t,o,n,i),a=this._positionChanged(r,t),l=n||!mo(r,o)||a;return l&&(this._active=r,(s.enabled||s.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,n))),l}_getActiveElements(t,n,i,s){const o=this.options;if(t.type==="mouseout")return[];if(!s)return n.filter(a=>this.chart.data.datasets[a.datasetIndex]&&this.chart.getDatasetMeta(a.datasetIndex).controller.getParsed(a.index)!==void 0);const r=this.chart.getElementsAtEventForMode(t,o.mode,o,i);return o.reverse&&r.reverse(),r}_positionChanged(t,n){const{caretX:i,caretY:s,options:o}=this,r=Bi[o.position].call(this,t,n);return r!==!1&&(i!==r.x||s!==r.y)}}D(Ir,"positioners",Bi);var vb={id:"tooltip",_element:Ir,positioners:Bi,afterInit(e,t,n){n&&(e.tooltip=new Ir({chart:e,options:n}))},beforeUpdate(e,t,n){e.tooltip&&e.tooltip.initialize(n)},reset(e,t,n){e.tooltip&&e.tooltip.initialize(n)},afterDraw(e){const t=e.tooltip;if(t&&t._willRender()){const n={tooltip:t};if(e.notifyPlugins("beforeTooltipDraw",{...n,cancelable:!0})===!1)return;t.draw(e.ctx),e.notifyPlugins("afterTooltipDraw",n)}},afterEvent(e,t){if(e.tooltip){const n=t.replay;e.tooltip.handleEvent(t.event,n,t.inChartArea)&&(t.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(e,t)=>t.bodyFont.size,boxWidth:(e,t)=>t.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:Gh},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:e=>e!=="filter"&&e!=="itemSort"&&e!=="external",_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]},_b=Object.freeze({__proto__:null,Colors:T_,Decimation:R_,Filler:tb,Legend:rb,SubTitle:cb,Title:lb,Tooltip:vb});const bb=(e,t,n,i)=>(typeof t=="string"?(n=e.push(t)-1,i.unshift({index:n,label:t})):isNaN(t)&&(n=null),n);function xb(e,t,n,i){const s=e.indexOf(t);if(s===-1)return bb(e,t,n,i);const o=e.lastIndexOf(t);return s!==o?n:s}const yb=(e,t)=>e===null?null:At(Math.round(e),0,t);function Wl(e){const t=this.getLabels();return e>=0&&en.length-1?null:this.getPixelForValue(n[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}}D(Fr,"id","category"),D(Fr,"defaults",{ticks:{callback:Wl}});function wb(e,t){const n=[],{bounds:s,step:o,min:r,max:a,precision:l,count:c,maxTicks:h,maxDigits:u,includeBounds:f}=e,d=o||1,g=h-1,{min:m,max:p}=t,v=!$(r),_=!$(a),x=!$(c),y=(p-m)/(u+1);let w=za((p-m)/g/d)*d,S,M,P,E;if(w<1e-14&&!v&&!_)return[{value:m},{value:p}];E=Math.ceil(p/w)-Math.floor(m/w),E>g&&(w=za(E*w/g/d)*d),$(l)||(S=Math.pow(10,l),w=Math.ceil(w*S)/S),s==="ticks"?(M=Math.floor(m/w)*w,P=Math.ceil(p/w)*w):(M=m,P=p),v&&_&&o&&gp((a-r)/o,w/1e3)?(E=Math.round(Math.min((a-r)/w,h)),w=(a-r)/E,M=r,P=a):x?(M=v?r:M,P=_?a:P,E=c-1,w=(P-M)/E):(E=(P-M)/w,Xi(E,Math.round(E),w/1e3)?E=Math.round(E):E=Math.ceil(E));const I=Math.max(Na(w),Na(M));S=Math.pow(10,$(l)?I:l),M=Math.round(M*S)/S,P=Math.round(P*S)/S;let j=0;for(v&&(f&&M!==r?(n.push({value:r}),Ma)break;n.push({value:O})}return _&&f&&P!==a?n.length&&Xi(n[n.length-1].value,a,Hl(a,y,e))?n[n.length-1].value=a:n.push({value:a}):(!_||P===a)&&n.push({value:P}),n}function Hl(e,t,{horizontal:n,minRotation:i}){const s=be(i),o=(n?Math.sin(s):Math.cos(s))||.001,r=.75*t*(""+e).length;return Math.min(t/o,r)}class ko extends ti{constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._endValue=void 0,this._valueRange=0}parse(t,n){return $(t)||(typeof t=="number"||t instanceof Number)&&!isFinite(+t)?null:+t}handleTickRangeOptions(){const{beginAtZero:t}=this.options,{minDefined:n,maxDefined:i}=this.getUserBounds();let{min:s,max:o}=this;const r=l=>s=n?s:l,a=l=>o=i?o:l;if(t){const l=Le(s),c=Le(o);l<0&&c<0?a(0):l>0&&c>0&&r(0)}if(s===o){let l=o===0?1:Math.abs(o*.05);a(o+l),t||r(s-l)}this.min=s,this.max=o}getTickLimit(){const t=this.options.ticks;let{maxTicksLimit:n,stepSize:i}=t,s;return i?(s=Math.ceil(this.max/i)-Math.floor(this.min/i)+1,s>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${i} would result generating up to ${s} ticks. Limiting to 1000.`),s=1e3)):(s=this.computeTickLimit(),n=n||11),n&&(s=Math.min(n,s)),s}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this.options,n=t.ticks;let i=this.getTickLimit();i=Math.max(2,i);const s={maxTicks:i,bounds:t.bounds,min:t.min,max:t.max,precision:n.precision,step:n.stepSize,count:n.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:n.minRotation||0,includeBounds:n.includeBounds!==!1},o=this._range||this,r=wb(s,o);return t.bounds==="ticks"&&rh(r,this,"value"),t.reverse?(r.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),r}configure(){const t=this.ticks;let n=this.min,i=this.max;if(super.configure(),this.options.offset&&t.length){const s=(i-n)/Math.max(t.length-1,1)/2;n-=s,i+=s}this._startValue=n,this._endValue=i,this._valueRange=i-n}getLabelForValue(t){return ws(t,this.chart.options.locale,this.options.ticks.format)}}class zr extends ko{determineDataLimits(){const{min:t,max:n}=this.getMinMax(!0);this.min=vt(t)?t:0,this.max=vt(n)?n:1,this.handleTickRangeOptions()}computeTickLimit(){const t=this.isHorizontal(),n=t?this.width:this.height,i=be(this.options.ticks.minRotation),s=(t?Math.sin(i):Math.cos(i))||.001,o=this._resolveTickFontOptions(0);return Math.ceil(n/Math.min(40,o.lineHeight/s))}getPixelForValue(t){return t===null?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}}D(zr,"id","linear"),D(zr,"defaults",{ticks:{callback:Eo.formatters.numeric}});const as=e=>Math.floor(hn(e)),Pn=(e,t)=>Math.pow(10,as(e)+t);function Yl(e){return e/Math.pow(10,as(e))===1}function Ul(e,t,n){const i=Math.pow(10,n),s=Math.floor(e/i);return Math.ceil(t/i)-s}function kb(e,t){const n=t-e;let i=as(n);for(;Ul(e,t,i)>10;)i++;for(;Ul(e,t,i)<10;)i--;return Math.min(i,as(e))}function Mb(e,{min:t,max:n}){t=Gt(e.min,t);const i=[],s=as(t);let o=kb(t,n),r=o<0?Math.pow(10,Math.abs(o)):1;const a=Math.pow(10,o),l=s>o?Math.pow(10,s):0,c=Math.round((t-l)*r)/r,h=Math.floor((t-l)/a/10)*a*10;let u=Math.floor((c-h)/Math.pow(10,o)),f=Gt(e.min,Math.round((l+h+u*Math.pow(10,o))*r)/r);for(;f=10?u=u<15?15:20:u++,u>=20&&(o++,u=2,r=o>=0?1:r),f=Math.round((l+h+u*Math.pow(10,o))*r)/r;const d=Gt(e.max,f);return i.push({value:d,major:Yl(d),significand:u}),i}class Nr extends ti{constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(t,n){const i=ko.prototype.parse.apply(this,[t,n]);if(i===0){this._zero=!0;return}return vt(i)&&i>0?i:null}determineDataLimits(){const{min:t,max:n}=this.getMinMax(!0);this.min=vt(t)?Math.max(0,t):null,this.max=vt(n)?Math.max(0,n):null,this.options.beginAtZero&&(this._zero=!0),this._zero&&this.min!==this._suggestedMin&&!vt(this._userMin)&&(this.min=t===Pn(this.min,0)?Pn(this.min,-1):Pn(this.min,0)),this.handleTickRangeOptions()}handleTickRangeOptions(){const{minDefined:t,maxDefined:n}=this.getUserBounds();let i=this.min,s=this.max;const o=a=>i=t?i:a,r=a=>s=n?s:a;i===s&&(i<=0?(o(1),r(10)):(o(Pn(i,-1)),r(Pn(s,1)))),i<=0&&o(Pn(s,-1)),s<=0&&r(Pn(i,1)),this.min=i,this.max=s}buildTicks(){const t=this.options,n={min:this._userMin,max:this._userMax},i=Mb(n,this);return t.bounds==="ticks"&&rh(i,this,"value"),t.reverse?(i.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),i}getLabelForValue(t){return t===void 0?"0":ws(t,this.chart.options.locale,this.options.ticks.format)}configure(){const t=this.min;super.configure(),this._startValue=hn(t),this._valueRange=hn(this.max)-hn(t)}getPixelForValue(t){return(t===void 0||t===0)&&(t=this.min),t===null||isNaN(t)?NaN:this.getPixelForDecimal(t===this.min?0:(hn(t)-this._startValue)/this._valueRange)}getValueForPixel(t){const n=this.getDecimalForPixel(t);return Math.pow(10,this._startValue+n*this._valueRange)}}D(Nr,"id","logarithmic"),D(Nr,"defaults",{ticks:{callback:Eo.formatters.logarithmic,major:{enabled:!0}}});function Br(e){const t=e.ticks;if(t.display&&e.display){const n=Et(t.backdropPadding);return W(t.font&&t.font.size,pt.font.size)+n.height}return 0}function Sb(e,t,n){return n=dt(n)?n:[n],{w:Op(e,t.string,n),h:n.length*t.lineHeight}}function Xl(e,t,n,i,s){return e===i||e===s?{start:t-n/2,end:t+n/2}:es?{start:t-n,end:t}:{start:t,end:t+n}}function Pb(e){const t={l:e.left+e._padding.left,r:e.right-e._padding.right,t:e.top+e._padding.top,b:e.bottom-e._padding.bottom},n=Object.assign({},t),i=[],s=[],o=e._pointLabels.length,r=e.options.pointLabels,a=r.centerPointLabels?st/o:0;for(let l=0;lt.r&&(a=(i.end-t.r)/o,e.r=Math.max(e.r,t.r+a)),s.startt.b&&(l=(s.end-t.b)/r,e.b=Math.max(e.b,t.b+l))}function Ab(e,t,n){const i=e.drawingArea,{extra:s,additionalAngle:o,padding:r,size:a}=n,l=e.getPointPosition(t,i+s+r,o),c=Math.round(oa(It(l.angle+xt))),h=Lb(l.y,a.h,c),u=Ob(c),f=Eb(l.x,a.w,u);return{visible:!0,x:l.x,y:h,textAlign:u,left:f,top:h,right:f+a.w,bottom:h+a.h}}function Db(e,t){if(!t)return!0;const{left:n,top:i,right:s,bottom:o}=e;return!(qe({x:n,y:i},t)||qe({x:n,y:o},t)||qe({x:s,y:i},t)||qe({x:s,y:o},t))}function Tb(e,t,n){const i=[],s=e._pointLabels.length,o=e.options,{centerPointLabels:r,display:a}=o.pointLabels,l={extra:Br(o)/2,additionalAngle:r?st/s:0};let c;for(let h=0;h270||n<90)&&(e-=t),e}function Rb(e,t,n){const{left:i,top:s,right:o,bottom:r}=n,{backdropColor:a}=t;if(!$(a)){const l=Un(t.borderRadius),c=Et(t.backdropPadding);e.fillStyle=a;const h=i-c.left,u=s-c.top,f=o-i+c.width,d=r-s+c.height;Object.values(l).some(g=>g!==0)?(e.beginPath(),os(e,{x:h,y:u,w:f,h:d,radius:l}),e.fill()):e.fillRect(h,u,f,d)}}function Ib(e,t){const{ctx:n,options:{pointLabels:i}}=e;for(let s=t-1;s>=0;s--){const o=e._pointLabelItems[s];if(!o.visible)continue;const r=i.setContext(e.getPointLabelContext(s));Rb(n,r,o);const a=wt(r.font),{x:l,y:c,textAlign:h}=o;Zn(n,e._pointLabels[s],l,c+a.lineHeight/2,a,{color:r.color,textAlign:h,textBaseline:"middle"})}}function Jh(e,t,n,i){const{ctx:s}=e;if(n)s.arc(e.xCenter,e.yCenter,t,0,gt);else{let o=e.getPointPosition(0,t);s.moveTo(o.x,o.y);for(let r=1;r{const s=ht(this.options.pointLabels.callback,[n,i],this);return s||s===0?s:""}).filter((n,i)=>this.chart.getDataVisibility(i))}fit(){const t=this.options;t.display&&t.pointLabels.display?Pb(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(t,n,i,s){this.xCenter+=Math.floor((t-n)/2),this.yCenter+=Math.floor((i-s)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(t,n,i,s))}getIndexAngle(t){const n=gt/(this._pointLabels.length||1),i=this.options.startAngle||0;return It(t*n+be(i))}getDistanceFromCenterForValue(t){if($(t))return NaN;const n=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-t)*n:(t-this.min)*n}getValueForDistanceFromCenter(t){if($(t))return NaN;const n=t/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-n:this.min+n}getPointLabelContext(t){const n=this._pointLabels||[];if(t>=0&&t{if(u!==0||u===0&&this.min<0){l=this.getDistanceFromCenterForValue(h.value);const f=this.getContext(u),d=s.setContext(f),g=o.setContext(f);Fb(this,d,l,r,g)}}),i.display){for(t.save(),a=r-1;a>=0;a--){const h=i.setContext(this.getPointLabelContext(a)),{color:u,lineWidth:f}=h;!f||!u||(t.lineWidth=f,t.strokeStyle=u,t.setLineDash(h.borderDash),t.lineDashOffset=h.borderDashOffset,l=this.getDistanceFromCenterForValue(n.reverse?this.min:this.max),c=this.getPointPosition(a,l),t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(c.x,c.y),t.stroke())}t.restore()}}drawBorder(){}drawLabels(){const t=this.ctx,n=this.options,i=n.ticks;if(!i.display)return;const s=this.getIndexAngle(0);let o,r;t.save(),t.translate(this.xCenter,this.yCenter),t.rotate(s),t.textAlign="center",t.textBaseline="middle",this.ticks.forEach((a,l)=>{if(l===0&&this.min>=0&&!n.reverse)return;const c=i.setContext(this.getContext(l)),h=wt(c.font);if(o=this.getDistanceFromCenterForValue(this.ticks[l].value),c.showLabelBackdrop){t.font=h.string,r=t.measureText(a.label).width,t.fillStyle=c.backdropColor;const u=Et(c.backdropPadding);t.fillRect(-r/2-u.left,-o-h.size/2-u.top,r+u.width,h.size+u.height)}Zn(t,a.label,0,-o,h,{color:c.color,strokeColor:c.textStrokeColor,strokeWidth:c.textStrokeWidth})}),t.restore()}drawTitle(){}}D(ji,"id","radialLinear"),D(ji,"defaults",{display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,lineWidth:1,borderDash:[],borderDashOffset:0},grid:{circular:!1},startAngle:0,ticks:{showLabelBackdrop:!0,callback:Eo.formatters.numeric},pointLabels:{backdropColor:void 0,backdropPadding:2,display:!0,font:{size:10},callback(t){return t},padding:5,centerPointLabels:!1}}),D(ji,"defaultRoutes",{"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"}),D(ji,"descriptors",{angleLines:{_fallback:"grid"}});const No={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Yt=Object.keys(No);function ql(e,t){return e-t}function $l(e,t){if($(t))return null;const n=e._adapter,{parser:i,round:s,isoWeekday:o}=e._parseOpts;let r=t;return typeof i=="function"&&(r=i(r)),vt(r)||(r=typeof i=="string"?n.parse(r,i):n.parse(r)),r===null?null:(s&&(r=s==="week"&&(xi(o)||o===!0)?n.startOf(r,"isoWeek",o):n.startOf(r,s)),+r)}function Kl(e,t,n,i){const s=Yt.length;for(let o=Yt.indexOf(e);o=Yt.indexOf(n);o--){const r=Yt[o];if(No[r].common&&e._adapter.diff(s,i,r)>=t-1)return r}return Yt[n?Yt.indexOf(n):0]}function Bb(e){for(let t=Yt.indexOf(e)+1,n=Yt.length;t=t?n[i]:n[s];e[o]=!0}}function jb(e,t,n,i){const s=e._adapter,o=+s.startOf(t[0].value,i),r=t[t.length-1].value;let a,l;for(a=o;a<=r;a=+s.add(a,1,i))l=n[a],l>=0&&(t[l].major=!0);return t}function Jl(e,t,n){const i=[],s={},o=t.length;let r,a;for(r=0;r+t.value))}initOffsets(t=[]){let n=0,i=0,s,o;this.options.offset&&t.length&&(s=this.getDecimalForValue(t[0]),t.length===1?n=1-s:n=(this.getDecimalForValue(t[1])-s)/2,o=this.getDecimalForValue(t[t.length-1]),t.length===1?i=o:i=(o-this.getDecimalForValue(t[t.length-2]))/2);const r=t.length<3?.5:.25;n=At(n,0,r),i=At(i,0,r),this._offsets={start:n,end:i,factor:1/(n+1+i)}}_generate(){const t=this._adapter,n=this.min,i=this.max,s=this.options,o=s.time,r=o.unit||Kl(o.minUnit,n,i,this._getLabelCapacity(n)),a=W(s.ticks.stepSize,1),l=r==="week"?o.isoWeekday:!1,c=xi(l)||l===!0,h={};let u=n,f,d;if(c&&(u=+t.startOf(u,"isoWeek",l)),u=+t.startOf(u,c?"day":r),t.diff(i,n,r)>1e5*a)throw new Error(n+" and "+i+" are too far apart with stepSize of "+a+" "+r);const g=s.ticks.source==="data"&&this.getDataTimestamps();for(f=u,d=0;f+m)}getLabelForValue(t){const n=this._adapter,i=this.options.time;return i.tooltipFormat?n.format(t,i.tooltipFormat):n.format(t,i.displayFormats.datetime)}format(t,n){const s=this.options.time.displayFormats,o=this._unit,r=n||s[o];return this._adapter.format(t,r)}_tickFormatFunction(t,n,i,s){const o=this.options,r=o.ticks.callback;if(r)return ht(r,[t,n,i],this);const a=o.time.displayFormats,l=this._unit,c=this._majorUnit,h=l&&a[l],u=c&&a[c],f=i[n],d=c&&u&&f&&f.major;return this._adapter.format(t,s||(d?u:h))}generateTickLabels(t){let n,i,s;for(n=0,i=t.length;n0?a:1}getDataTimestamps(){let t=this._cache.data||[],n,i;if(t.length)return t;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(n=0,i=s.length;n=e[i].pos&&t<=e[s].pos&&({lo:i,hi:s}=Xe(e,"pos",t)),{pos:o,time:a}=e[i],{pos:r,time:l}=e[s]):(t>=e[i].time&&t<=e[s].time&&({lo:i,hi:s}=Xe(e,"time",t)),{time:o,pos:a}=e[i],{time:r,pos:l}=e[s]);const c=r-o;return c?a+(l-a)*(t-o)/c:a}class jr extends ls{constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this._getTimestampsForTable(),n=this._table=this.buildLookupTable(t);this._minPos=js(n,this.min),this._tableRange=js(n,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){const{min:n,max:i}=this,s=[],o=[];let r,a,l,c,h;for(r=0,a=t.length;r=n&&c<=i&&s.push(c);if(s.length<2)return[{time:n,pos:0},{time:i,pos:1}];for(r=0,a=s.length;rs-o)}_getTimestampsForTable(){let t=this._cache.all||[];if(t.length)return t;const n=this.getDataTimestamps(),i=this.getLabelTimestamps();return n.length&&i.length?t=this.normalize(n.concat(i)):t=n.length?n:i,t=this._cache.all=t,t}getDecimalForValue(t){return(js(this._table,t)-this._minPos)/this._tableRange}getValueForPixel(t){const n=this._offsets,i=this.getDecimalForPixel(t)/n.factor-n.end;return js(this._table,i*this._tableRange+this._minPos,!0)}}D(jr,"id","timeseries"),D(jr,"defaults",ls.defaults);var Vb=Object.freeze({__proto__:null,CategoryScale:Fr,LinearScale:zr,LogarithmicScale:Nr,RadialLinearScale:ji,TimeScale:ls,TimeSeriesScale:jr});const Wb=[qm,k_,_b,Vb];Ve.register(...Wb);/*! - * chartjs-plugin-datalabels v2.2.0 - * https://chartjs-plugin-datalabels.netlify.app - * (c) 2017-2022 chartjs-plugin-datalabels contributors - * Released under the MIT license - */var Zl=(function(){if(typeof window<"u"){if(window.devicePixelRatio)return window.devicePixelRatio;var e=window.screen;if(e)return(e.deviceXDPI||1)/(e.logicalXDPI||1)}return 1})(),Zi={toTextLines:function(e){var t=[],n;for(e=[].concat(e);e.length;)n=e.pop(),typeof n=="string"?t.unshift.apply(t,n.split(` -`)):Array.isArray(n)?e.push.apply(e,n):$(e)||t.unshift(""+n);return t},textSize:function(e,t,n){var i=[].concat(t),s=i.length,o=e.font,r=0,a;for(e.font=n.string,a=0;an.right&&(i|=Qh),tn.bottom&&(i|=tu),i}function Ub(e,t){for(var n=e.x0,i=e.y0,s=e.x1,o=e.y1,r=Vs(n,i,t),a=Vs(s,o,t),l,c,h;!(!(r|a)||r&a);)l=r||a,l&eu?(c=n+(s-n)*(t.top-i)/(o-i),h=t.top):l&tu?(c=n+(s-n)*(t.bottom-i)/(o-i),h=t.bottom):l&Qh?(h=i+(o-i)*(t.right-n)/(s-n),c=t.right):l&Zh&&(h=i+(o-i)*(t.left-n)/(s-n),c=t.left),l===r?(n=c,i=h,r=Vs(n,i,t)):(s=c,o=h,a=Vs(s,o,t));return{x0:n,x1:s,y0:i,y1:o}}function Ws(e,t){var n=t.anchor,i=e,s,o;return t.clamp&&(i=Ub(i,t.area)),n==="start"?(s=i.x0,o=i.y0):n==="end"?(s=i.x1,o=i.y1):(s=(i.x0+i.x1)/2,o=(i.y0+i.y1)/2),Hb(s,o,e.vx,e.vy,t.align)}var Hs={arc:function(e,t){var n=(e.startAngle+e.endAngle)/2,i=Math.cos(n),s=Math.sin(n),o=e.innerRadius,r=e.outerRadius;return Ws({x0:e.x+i*o,y0:e.y+s*o,x1:e.x+i*r,y1:e.y+s*r,vx:i,vy:s},t)},point:function(e,t){var n=cr(e,t.origin),i=n.x*e.options.radius,s=n.y*e.options.radius;return Ws({x0:e.x-i,y0:e.y-s,x1:e.x+i,y1:e.y+s,vx:n.x,vy:n.y},t)},bar:function(e,t){var n=cr(e,t.origin),i=e.x,s=e.y,o=0,r=0;return e.horizontal?(i=Math.min(e.x,e.base),o=Math.abs(e.base-e.x)):(s=Math.min(e.y,e.base),r=Math.abs(e.base-e.y)),Ws({x0:i,y0:s+r,x1:i+o,y1:s,vx:n.x,vy:n.y},t)},fallback:function(e,t){var n=cr(e,t.origin);return Ws({x0:e.x,y0:e.y,x1:e.x+(e.width||0),y1:e.y+(e.height||0),vx:n.x,vy:n.y},t)}},$e=Zi.rasterize;function Xb(e){var t=e.borderWidth||0,n=e.padding,i=e.size.height,s=e.size.width,o=-s/2,r=-i/2;return{frame:{x:o-n.left-t,y:r-n.top-t,w:s+n.width+t*2,h:i+n.height+t*2},text:{x:o,y:r,w:s,h:i}}}function qb(e,t){var n=t.chart.getDatasetMeta(t.datasetIndex).vScale;if(!n)return null;if(n.xCenter!==void 0&&n.yCenter!==void 0)return{x:n.xCenter,y:n.yCenter};var i=n.getBasePixel();return e.horizontal?{x:i,y:null}:{x:null,y:i}}function $b(e){return e instanceof ri?Hs.arc:e instanceof Gi?Hs.point:e instanceof Ji?Hs.bar:Hs.fallback}function Kb(e,t,n,i,s,o){var r=Math.PI/2;if(o){var a=Math.min(o,s/2,i/2),l=t+a,c=n+a,h=t+i-a,u=n+s-a;e.moveTo(t,c),li.x+i.w+n*2||e.y>i.y+i.h+n*2)},intersects:function(e){var t=this._points(),n=e._points(),i=[Ys(t[0],t[1]),Ys(t[0],t[3])],s,o,r;for(this._rotation!==e._rotation&&i.push(Ys(n[0],n[1]),Ys(n[0],n[3])),s=0;s=0;--n)for(s=e[n].$layout,i=n-1;i>=0&&s._visible;--i)o=e[i].$layout,o._visible&&s._box.intersects(o._box)&&t(s,o);return e}function ix(e){var t,n,i,s,o,r,a;for(t=0,n=e.length;tl.getProps([c],!0)[c]}),o=i.geometry(),r=su(a,i.model(),o),s._box.update(r,o,i.rotation()));return nx(e,function(l,c){var h=l._hidable,u=c._hidable;h&&u||u?c._visible=!1:h&&(l._visible=!1)})}var Qi={prepare:function(e){var t=[],n,i,s,o,r;for(n=0,s=e.length;n=0;--n)if(i=e[n].$layout,i&&i._visible&&i._box.contains(t))return e[n];return null},draw:function(e,t){var n,i,s,o,r,a;for(n=0,i=t.length;n

                  ');function Us(e,t){at(t,!0);let n=Xt(t,"height",3,240),i=B(null),s=null;function o(d,g){return typeof document>"u"?g:getComputedStyle(document.documentElement).getPropertyValue(d).trim()||g}function r(){return[o("--accent","#eaa45d"),o("--chart-2","#6aa3d8"),o("--chart-3","#7fb77e"),o("--chart-4","#d88c8c"),o("--chart-5","#b39ddb"),o("--chart-6","#e0a458")]}function a(){var _,x;if(!s)return;const d=r(),g=o("--text-primary","#1f2328"),m=o("--chart-grid","#dddddd"),p=(s.data.labels??[]).map((y,w)=>d[w%d.length]);if(t.spec.type==="pie")s.data.datasets[0].backgroundColor=p;else{const y=s.data.datasets[0],w=s.data.datasets[1];y.backgroundColor=p.map(S=>S+"80"),y.borderColor=p,y.borderWidth=2,w.backgroundColor=p}const v=s.options;if((x=(_=v.plugins)==null?void 0:_.legend)!=null&&x.labels&&(v.plugins.legend.labels.color=g),v.scales)for(const y of Object.values(v.scales))y&&(y.ticks={...y.ticks,color:g},y.grid={...y.grid,color:m},y.title&&(y.title.color=g));s.update()}Vn(()=>{if(!b(i))return;const d=b(i),g=Ge(()=>new Ve(d,{...t.spec,plugins:[hx]}));return s=g,Ge(()=>a()),()=>{s=null,g.destroy()}}),Vn(()=>{const d=t.spec;s&&(s.data.labels=d.data.labels??[],d.data.datasets.forEach((g,m)=>{s.data.datasets[m]&&(s.data.datasets[m].data=g.data)}),a())}),Vn(()=>{Yn.current,s&&a()});var l=ux(),c=C(l),h=C(c),u=T(c,2),f=C(u);Oo(f,d=>L(i,d),()=>b(i)),G(()=>{q(h,t.title),Gc(u,`height: ${n()??""}px`)}),A(e,l),lt()}var fx=R('
                  Calls
                  Input tokens
                  Output tokens
                  Total tokens
                  ');function dx(e,t){at(t,!0);const n=it(()=>Object.values(t.stats).reduce((p,v)=>({calls:p.calls+v.num_times_called,input:p.input+v.input_tokens,output:p.output+v.output_tokens}),{calls:0,input:0,output:0}));var i=fx(),s=C(i),o=T(C(s),2),r=C(o),a=T(s,2),l=T(C(a),2),c=C(l),h=T(a,2),u=T(C(h),2),f=C(u),d=T(h,2),g=T(C(d),2),m=C(g);G((p,v,_,x)=>{q(r,p),q(c,v),q(f,_),q(m,x)},[()=>Cs(b(n).calls),()=>Cs(b(n).input),()=>Cs(b(n).output),()=>Cs(b(n).input+b(n).output)]),A(e,i),lt()}var gx=R('
                  ',1),px=R('
                  No tool stats collected yet.
                  '),mx=R('
                  ',1);function vx(e,t){at(t,!0);const n=it(()=>Object.keys(Se.stats).length>0);Ze(()=>{Se.refresh()});var i=mx(),s=rt(i),o=C(s);qt(o,{onclick:()=>Se.refresh(),children:(h,u)=>{var f=yt("Refresh Stats");A(h,f)},$$slots:{default:!0}});var r=T(o,2);qt(r,{onclick:()=>Se.clear(),children:(h,u)=>{var f=yt("Clear Stats");A(h,f)},$$slots:{default:!0}});var a=T(s,2);{var l=h=>{var u=gx(),f=rt(u);dx(f,{get stats(){return Se.stats}});var d=T(f,2),g=C(d),m=T(d,2),p=C(m);{let w=it(()=>$o(Se.stats,"num_times_called"));Us(p,{title:"Tool Calls",height:360,get spec(){return b(w)}})}var v=T(p,2);{let w=it(()=>$o(Se.stats,"input_tokens"));Us(v,{title:"Input Tokens",height:360,get spec(){return b(w)}})}var _=T(v,2);{let w=it(()=>$o(Se.stats,"output_tokens"));Us(_,{title:"Output Tokens",height:360,get spec(){return b(w)}})}var x=T(m,2),y=C(x);{let w=it(()=>Rg(Se.stats));Us(y,{title:"Token Usage (by tool)",height:460,get spec(){return b(w)}})}G(()=>q(g,`Token estimator: ${Se.estimator??""}`)),A(h,u)},c=h=>{var u=px();A(h,u)};tt(a,h=>{b(n)?h(l):h(c,-1)})}A(e,i),lt()}function _x(){let e=B(null);return{get active(){return b(e)},open(t){L(e,t,!0)},close(){L(e,null)}}}const pe=_x();var bx=R("

                  "),xx=R(''),yx=R('');function ks(e,t){at(t,!0);let n=Xt(t,"open",3,!1),i=Xt(t,"title",3,""),s=Xt(t,"error",3,""),o=B(null),r=null;Vn(()=>{n()&&b(o)&&(r=document.activeElement,b(o).focus())}),Jc(()=>r==null?void 0:r.focus());function a(u){if(u.key==="Escape"){t.onclose();return}if(u.key!=="Tab"||!n()||!b(o))return;const f=b(o).querySelectorAll('a[href], button:not([disabled]), input, textarea, select, [tabindex]:not([tabindex="-1"])');if(f.length===0)return;const d=f[0],g=f[f.length-1],m=document.activeElement;u.shiftKey&&m===d?(u.preventDefault(),g.focus()):!u.shiftKey&&m===g&&(u.preventDefault(),d.focus())}var l=zt();$c("keydown",yr,a);var c=rt(l);{var h=u=>{var f=yx(),d=C(f),g=C(d),m=T(g,2);{var p=y=>{var w=bx(),S=C(w);G(()=>q(S,i())),A(y,w)};tt(m,y=>{i()&&y(p)})}var v=T(m,2);{var _=y=>{var w=xx(),S=C(w);G(()=>q(S,s())),A(y,w)};tt(v,y=>{s()&&y(_)})}var x=T(v,2);bs(x,()=>t.children),Oo(d,y=>L(o,y),()=>b(o)),ut("click",f,function(...y){var w;(w=t.onclose)==null||w.apply(this,y)}),ut("keydown",f,a),ut("click",d,y=>y.stopPropagation()),ut("keydown",d,y=>{a(y),y.stopPropagation()}),ut("click",g,function(...y){var w;(w=t.onclose)==null||w.apply(this,y)}),A(u,f)};tt(c,u=>{n()&&u(h)})}A(e,l),lt()}Me(["click","keydown"]);function ki(){let e=B(!1),t=B("");return{get busy(){return b(e)},get error(){return b(t)},clearError(){L(t,"")},async run(n,i){L(e,!0),L(t,"");const s=await n();return L(e,!1),s.ok?(i(),s):(L(t,s.message??"",!0),s)}}}var wx=R(' ',1);function Bo(e,t){at(t,!0);let n=Xt(t,"title",3,""),i=Xt(t,"confirmLabel",3,"OK"),s=Xt(t,"variant",3,"primary");const o=ki();ks(e,{open:!0,get title(){return n()},get error(){return o.error},get onclose(){return t.onclose},children:(r,a)=>{var l=wx(),c=rt(l),h=C(c);bs(h,()=>t.children);var u=T(c,2),f=C(u);qt(f,{variant:"secondary",get onclick(){return t.onclose},children:(g,m)=>{var p=yt("Cancel");A(g,p)},$$slots:{default:!0}});var d=T(f,2);qt(d,{get variant(){return s()},get disabled(){return o.busy},onclick:()=>o.run(t.onconfirm,t.onclose),children:(g,m)=>{var p=zt(),v=rt(p);{var _=y=>{xs(y)},x=y=>{var w=yt();G(()=>q(w,i())),A(y,w)};tt(v,y=>{o.busy?y(_):y(x,-1)})}A(g,p)},$$slots:{default:!0}}),A(r,l)},$$slots:{default:!0}}),lt()}function kx(e,t){at(t,!0);async function n(){const i=await ye(()=>ud());return i.ok&&setTimeout(()=>window.close(),1e3),i}Bo(e,{title:"Shutdown Server",confirmLabel:"Shutdown",variant:"danger",onconfirm:n,get onclose(){return t.onclose},children:(i,s)=>{var o=yt("Shut down the Serena server?");A(i,o)},$$slots:{default:!0}}),lt()}function Mx(e,t){at(t,!0);function n(){Oe.clearCancelError(),t.onclose()}Bo(e,{onconfirm:()=>Oe.cancel(t.execution),onclose:n,children:(i,s)=>{var o=yt(`Are you sure? The execution will continue running until timeout, it will simply no longer be in - the queue. Abandoning a running execution is only advised as a measure for unblocking Serena.`);A(i,o)},$$slots:{default:!0}}),lt()}var Sx=R('
                • '),Px=R('
                    '),Cx=R('
                    No options available
                    '),Ax=R('
                    ');function Dx(e,t){at(t,!0);let n=Xt(t,"value",3,""),i=Xt(t,"placeholder",3,"Type to filter…"),s=B(Tt(n())),o=B(!1),r=B(null);const a=it(()=>t.options.filter(m=>m.toLowerCase().includes(b(s).toLowerCase())));function l(m){L(s,m,!0),L(o,!1),t.onselect(m)}function c(m){var v;const p=m.relatedTarget;p&&((v=b(r))!=null&&v.contains(p))||L(o,!1)}function h(m,p){(m.key==="Enter"||m.key===" ")&&(m.preventDefault(),l(p))}var u=Ax(),f=C(u),d=T(f,4);{var g=m=>{var p=zt(),v=rt(p);{var _=y=>{var w=Px();le(w,20,()=>b(a),S=>S,(S,M)=>{var P=Sx(),E=C(P);G(()=>{Pt(P,"aria-selected",M===b(s)),q(E,M)}),ut("click",P,()=>l(M)),ut("keydown",P,I=>h(I,M)),A(S,P)}),A(y,w)},x=y=>{var w=Cx();A(y,w)};tt(v,y=>{b(a).length?y(_):y(x,-1)})}A(m,p)};tt(d,m=>{b(o)&&m(g)})}Oo(u,m=>L(r,m),()=>b(r)),G(()=>Pt(f,"placeholder",i())),ut("focusout",u,c),$c("focus",f,()=>L(o,!0)),ut("input",f,()=>L(o,!0)),es(f,()=>b(s),m=>L(s,m)),A(e,u),lt()}Me(["focusout","input","click","keydown"]);var Tx=R(` `,1);function Ox(e,t){at(t,!0);let n=B(Tt([])),i=B("");const s=ki();Ze(()=>{(async()=>L(n,(await fd()).languages,!0))()});function o(){b(i)&&s.run(()=>ye(()=>dd(b(i))),t.onclose)}ks(e,{open:!0,title:"Add Language",get error(){return s.error},get onclose(){return t.onclose},children:(r,a)=>{var l=Tx(),c=rt(l),h=T(C(c)),u=C(h),f=T(c,4);Dx(f,{get options(){return b(n)},get value(){return b(i)},onselect:p=>L(i,p,!0)});var d=T(f,2),g=C(d);qt(g,{variant:"secondary",get onclick(){return t.onclose},children:(p,v)=>{var _=yt("Cancel");A(p,_)},$$slots:{default:!0}});var m=T(g,2);{let p=it(()=>s.busy||!b(i));qt(m,{get disabled(){return b(p)},onclick:o,children:(v,_)=>{var x=zt(),y=rt(x);{var w=M=>{xs(M)},S=M=>{var P=yt("Add Language");A(M,P)};tt(y,M=>{s.busy?M(w):M(S,-1)})}A(v,x)},$$slots:{default:!0}})}G(()=>q(u,t.projectName)),A(r,l)},$$slots:{default:!0}}),lt()}var Ex=R("Remove language from configuration?",1);function Lx(e,t){at(t,!0),Bo(e,{onconfirm:()=>ye(()=>gd(t.language)),get onclose(){return t.onclose},children:(n,i)=>{var s=Ex(),o=T(rt(s)),r=C(o);G(()=>q(r,t.language)),A(n,s)},$$slots:{default:!0}}),lt()}function ru(e){return/^[A-Za-z0-9_]+(\/[A-Za-z0-9_]+)*$/.test(e)}function au(e){return!e||window.confirm("You have unsaved changes. Discard them?")}var Rx=R(' ',1),Ix=R(' ',1),Fx=R(' ',1);function zx(e,t){at(t,!0);let n=B(Tt(Ge(()=>t.name))),i=B(""),s=B(""),o=B(!1),r=B(""),a=B(!1),l=B(Tt(Ge(()=>t.name)));const c=it(()=>b(i)!==b(s)),h=ki(),u=ki();async function f(){var _;const p=await ye(()=>pd(t.name));if(!p.ok){L(r,p.message??"Failed to load memory.",!0);return}const v=((_=p.data)==null?void 0:_.content)??"";L(i,v,!0),L(s,v,!0),L(o,!0)}Ze(()=>{f()});function d(){au(b(c))&&t.onclose()}function g(){h.run(()=>ye(()=>Qc(b(n),b(i))),t.onclose)}function m(){if(!ru(b(l))||b(l)===b(n)){L(a,!1);return}u.run(()=>ye(()=>vd(b(n),b(l))),()=>{L(n,b(l),!0),L(a,!1)})}{let p=it(()=>b(r)||h.error||u.error);ks(e,{open:!0,get error(){return b(p)},onclose:d,children:(v,_)=>{var x=Fx(),y=rt(x),w=T(C(y));{var S=O=>{var F=Rx(),V=rt(F),z=T(V,2);G(()=>z.disabled=u.busy),ut("keydown",V,H=>H.key==="Enter"&&m()),es(V,()=>b(l),H=>L(l,H)),ut("click",z,m),A(O,F)},M=O=>{var F=Ix(),V=rt(F),z=C(V),H=T(V,2);G(()=>q(z,b(n))),ut("click",H,()=>{L(a,!0),L(l,b(n),!0)}),A(O,F)};tt(w,O=>{b(a)?O(S):O(M,-1)})}var P=T(y,2),E=T(P,2),I=C(E);qt(I,{variant:"secondary",onclick:d,children:(O,F)=>{var V=yt("Cancel");A(O,V)},$$slots:{default:!0}});var j=T(I,2);{let O=it(()=>!b(o)||h.busy);qt(j,{get disabled(){return b(O)},onclick:g,children:(F,V)=>{var z=yt("Save");A(F,z)},$$slots:{default:!0}})}es(P,()=>b(i),O=>L(i,O)),A(v,x)},$$slots:{default:!0}})}lt()}Me(["keydown","click"]);var Nx=R(` `,1);function Bx(e,t){at(t,!0);let n=B("");const i=it(()=>ru(b(n))),s=ki();function o(){b(i)&&s.run(()=>ye(()=>Qc(b(n),"")),()=>t.oncreated(b(n)))}ks(e,{open:!0,title:"Create Memory",get error(){return s.error},get onclose(){return t.onclose},children:(r,a)=>{var l=Nx(),c=rt(l),h=T(C(c)),u=C(h),f=T(c,4),d=T(f,2),g=C(d);qt(g,{variant:"secondary",get onclick(){return t.onclose},children:(p,v)=>{var _=yt("Cancel");A(p,_)},$$slots:{default:!0}});var m=T(g,2);{let p=it(()=>!b(i)||s.busy);qt(m,{get disabled(){return b(p)},onclick:o,children:(v,_)=>{var x=zt(),y=rt(x);{var w=M=>{xs(M)},S=M=>{var P=yt("Create");A(M,P)};tt(y,M=>{s.busy?M(w):M(S,-1)})}A(v,x)},$$slots:{default:!0}})}G(()=>q(u,t.projectName)),es(f,()=>b(n),p=>L(n,p)),A(r,l)},$$slots:{default:!0}}),lt()}var jx=R("Delete memory ?",1);function Vx(e,t){at(t,!0),Bo(e,{variant:"danger",onconfirm:()=>ye(()=>md(t.name)),get onclose(){return t.onclose},children:(n,i)=>{var s=jx(),o=T(rt(s)),r=C(o);G(()=>q(r,t.name)),A(n,s)},$$slots:{default:!0}}),lt()}var Wx=R(' ',1);function Hx(e,t){at(t,!0);let n=B(""),i=B(""),s=B(!1),o=B("");const r=it(()=>b(n)!==b(i)),a=ki();async function l(){var d;const u=await ye(()=>_d());if(!u.ok){L(o,u.message??"Failed to load configuration.",!0);return}const f=((d=u.data)==null?void 0:d.content)??"";L(n,f,!0),L(i,f,!0),L(s,!0)}Ze(()=>{l()});function c(){au(b(r))&&t.onclose()}function h(){a.run(()=>ye(()=>bd(b(n))),t.onclose)}{let u=it(()=>b(o)||a.error);ks(e,{open:!0,title:"Global Serena Configuration",get error(){return b(u)},onclose:c,children:(f,d)=>{var g=Wx(),m=T(rt(g),2),p=T(m,2),v=C(p);qt(v,{variant:"secondary",onclick:c,children:(x,y)=>{var w=yt("Cancel");A(x,w)},$$slots:{default:!0}});var _=T(v,2);{let x=it(()=>!b(s)||a.busy);qt(_,{get disabled(){return b(x)},onclick:h,children:(y,w)=>{var S=yt("Save");A(y,S)},$$slots:{default:!0}})}es(m,()=>b(n),x=>L(n,x)),A(f,g)},$$slots:{default:!0}})}lt()}function Yx(e,t){at(t,!0);const n=it(()=>{var l,c;return String(((c=(l=go.data)==null?void 0:l.active_project)==null?void 0:c.name)??"")}),i=it(()=>pe.active),s=()=>pe.close();var o=zt(),r=rt(o);{var a=l=>{var c=zt(),h=rt(c);{var u=x=>{kx(x,{onclose:s})},f=x=>{Mx(x,{get execution(){return b(i).execution},onclose:s})},d=x=>{Ox(x,{get projectName(){return b(n)},onclose:s})},g=x=>{Lx(x,{get language(){return b(i).language},onclose:s})},m=x=>{var y=zt(),w=rt(y);Ef(w,()=>b(i).name,S=>{zx(S,{get name(){return b(i).name},onclose:s})}),A(x,y)},p=x=>{Bx(x,{get projectName(){return b(n)},onclose:s,oncreated:y=>pe.open({kind:"editMemory",name:y})})},v=x=>{Vx(x,{get name(){return b(i).name},onclose:s})},_=x=>{Hx(x,{onclose:s})};tt(h,x=>{b(i).kind==="shutdown"?x(u):b(i).kind==="cancelExecution"?x(f,1):b(i).kind==="addLanguage"?x(d,2):b(i).kind==="removeLanguage"?x(g,3):b(i).kind==="editMemory"?x(m,4):b(i).kind==="createMemory"?x(p,5):b(i).kind==="deleteMemory"?x(v,6):b(i).kind==="editSerenaConfig"&&x(_,7)})}A(l,c)};tt(r,l=>{b(i)&&l(a)})}A(e,o),lt()}var Ux=R('
                    '),Xx=R('
                    '),qx=R('
                    '),$x=R('
                    ',1);function Kx(e,t){at(t,!0);let n=B("overview");Vn(()=>{var S,M;document.title=Ad((M=(S=go.data)==null?void 0:S.active_project)==null?void 0:M.name)});const i=S=>S.catch(M=>console.debug("poll failed",M)),s=Ps(()=>i(go.poll()),1e3),o=Ps(()=>i(Oe.pollQueued()),1e3),r=Ps(()=>i(Oe.pollLast()),1e3),a=Ps(()=>i(eo.poll()),1e3),l={config:s,queued:o,last:r,logs:a};function c(S){for(const M of Object.values(l))M.stop();for(const M of ed(S))l[M].start()}function h(S){L(n,S,!0),c(S)}Ze(()=>{Yn.init(),c("overview")});var u=$x(),f=rt(u),d=C(f);td(d,{get active(){return b(n)},onnavigate:h,onshutdown:()=>pe.open({kind:"shutdown"})});var g=T(d,2),m=C(g);{var p=S=>{var M=Ux(),P=C(M);xg(P,{onaddlanguage:()=>pe.open({kind:"addLanguage"}),onremovelanguage:E=>pe.open({kind:"removeLanguage",language:E}),oneditconfig:()=>pe.open({kind:"editSerenaConfig"}),onopenmemory:E=>pe.open({kind:"editMemory",name:E}),oncreatememory:()=>pe.open({kind:"createMemory"}),ondeletememory:E=>pe.open({kind:"deleteMemory",name:E}),oncancelexecution:E=>E.is_running?pe.open({kind:"cancelExecution",execution:E}):void Oe.cancel(E)}),A(S,M)};tt(m,S=>{b(n)==="overview"&&S(p)})}var v=T(m,2);{var _=S=>{var M=Xx(),P=C(M);Og(P,{}),A(S,M)};tt(v,S=>{b(n)==="logs"&&S(_)})}var x=T(v,2);{var y=S=>{var M=qx(),P=C(M);vx(P,{}),A(S,M)};tt(x,S=>{b(n)==="stats"&&S(y)})}var w=T(f,2);Yx(w,{}),A(e,u),lt()}Af(Kx,{target:document.getElementById("app")}); diff --git a/src/serena/resources/dashboard/assets/index-i8LjQRS6.css b/src/serena/resources/dashboard/assets/index-i8LjQRS6.css new file mode 100644 index 000000000..b04352a51 --- /dev/null +++ b/src/serena/resources/dashboard/assets/index-i8LjQRS6.css @@ -0,0 +1 @@ +:root{--bg: #f5f5f5;--bg-card: #ffffff;--bg-elevated: #ffffff;--bg-secondary-btn: #f0f2f5;--text-primary: #1f2328;--text-secondary: #3f4754;--text-muted: #6a737d;--border: #e3e6ea;--border-strong: #d0d7de;--accent: #eaa45d;--accent-hover: #dca662;--chart-2: #6aa3d8;--chart-3: #7fb77e;--chart-4: #d88c8c;--chart-5: #b39ddb;--chart-6: #e0a458;--chart-grid: #dddddd;--text-on-accent: #ffffff;--btn-disabled: #adb5bd;--tool-highlight: #fff3bf;--tool-highlight-text: #1f2328;--btn-secondary-hover: #e3e6ea;--stats-header: #f0f2f5;--success: #22c55e;--log-debug: #8b95a1;--log-info: #1f2328;--log-warning: #d97706;--log-error: #dc2626;--radius: 6px;--radius-sm: 4px;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-6: 24px;--space-8: 32px;--max-width: 1600px;--font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-mono: "JetBrains Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;--shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 1px 3px rgba(15, 23, 42, .06);--shadow-elevated: 0 4px 12px rgba(15, 23, 42, .08);--focus-ring: 0 0 0 3px rgba(234, 164, 93, .18)}[data-theme=dark]{--bg: #1a1a1a;--bg-card: #2d2d2d;--bg-elevated: #262b32;--bg-secondary-btn: #2d333b;--text-primary: #e6edf3;--text-secondary: #c9d1d9;--text-muted: #8b95a1;--border: #2d333b;--border-strong: #3d444d;--chart-grid: #444444;--btn-disabled: #4a5159;--tool-highlight: #f6c948;--tool-highlight-text: #15181c;--btn-secondary-hover: #3d444d;--stats-header: #262b32;--log-info: #e6edf3;--log-warning: #f59e0b;--log-error: #f87171;--shadow: 0 1px 2px rgba(0, 0, 0, .25), 0 1px 3px rgba(0, 0, 0, .35);--shadow-elevated: 0 4px 12px rgba(0, 0, 0, .45)}*{box-sizing:border-box}html{scrollbar-gutter:stable}body{margin:0;font-family:var(--font-sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background:var(--bg);color:var(--text-primary);transition:background-color .3s ease,color .3s ease}h1,h2,h3,h4,h5,h6{font-weight:600;letter-spacing:-.005em}code,pre,.mono{font-family:var(--font-mono)}a{color:inherit;text-decoration:none}input:focus-visible,textarea:focus-visible{outline:none;border-color:var(--accent);box-shadow:var(--focus-ring)}.modal-actions{display:flex;gap:var(--space-3);justify-content:flex-end;margin-top:var(--space-4)}.modal-info{color:var(--text-secondary)}.modal-hint{color:var(--text-muted);font-size:13px}.modal-input{width:100%;padding:var(--space-2);border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-card);color:var(--text-primary)}.modal-textarea{width:100%;font-family:var(--font-mono);font-size:13px;background:var(--bg-card);color:var(--text-primary);border:1px solid var(--border-strong);border-radius:var(--radius-sm);padding:var(--space-3)}.theme-toggle.svelte-ev54os{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer;color:var(--text-primary);padding:0;line-height:1}.theme-toggle.svelte-ev54os:hover{background:var(--bg-card)}.icon.svelte-ev54os{font-size:18px;line-height:1}.banner.svelte-1lo7lbo{position:relative;display:inline-flex;align-items:center}.banner.gold.svelte-1lo7lbo{display:flex;width:100%}.banner.gold.svelte-1lo7lbo a:where(.svelte-1lo7lbo){display:block;width:100%}.banner.svelte-1lo7lbo img:where(.svelte-1lo7lbo){max-height:90px;display:block}.banner.platinum.svelte-1lo7lbo img:where(.svelte-1lo7lbo){max-height:150px}.banner.gold.svelte-1lo7lbo img:where(.svelte-1lo7lbo){width:100%;height:auto;max-height:none;display:block}.header.svelte-xjepwq{display:flex;justify-content:space-between;align-items:center;gap:var(--space-6);padding:var(--space-6);background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);min-height:150px;max-width:var(--max-width);margin:0 auto}.header-left.svelte-xjepwq{display:flex;align-items:center;gap:var(--space-6)}.header-banner.svelte-xjepwq{display:flex;align-items:center}#serena-logo.svelte-xjepwq{height:130px}.header-nav.svelte-xjepwq{display:flex;flex-direction:column;align-items:flex-end;gap:var(--space-4)}.header-actions.svelte-xjepwq{position:relative;display:flex;gap:var(--space-2)}.icon-button.svelte-xjepwq{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer;color:var(--text-primary);padding:0;line-height:1}.icon-button.svelte-xjepwq:hover{background:var(--bg-card)}.icon-button.svelte-xjepwq .icon:where(.svelte-xjepwq){font-size:18px;line-height:1}.shutdown-button.svelte-xjepwq:hover{color:var(--danger, #c0392b);border-color:var(--danger, #c0392b)}.header-tabs.svelte-xjepwq{display:flex;gap:var(--space-5)}.header-tab.svelte-xjepwq{background:none;border:none;cursor:pointer;color:var(--text-primary);font-family:var(--font-sans);font-size:1.05rem;font-weight:600;letter-spacing:.01em;padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);border-bottom:3px solid transparent;transition:background-color .12s ease,color .12s ease,border-color .12s ease}.header-tab.svelte-xjepwq:hover{background:var(--bg-secondary-btn)}.header-tab.active.svelte-xjepwq{color:var(--accent);border-bottom-color:var(--accent)}.collapsible-header.svelte-oy8gea{margin:0;font-size:inherit;font-weight:inherit}.collapsible-trigger.svelte-oy8gea{display:flex;justify-content:space-between;align-items:center;cursor:pointer;width:100%;background:none;border:none;padding:0;text-align:left}.collapsible-title.svelte-oy8gea{text-transform:uppercase;letter-spacing:.04em;font-size:15px;font-weight:600;color:var(--text-muted)}.collapsible-trigger.svelte-oy8gea:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.toggle-icon.svelte-oy8gea{font-size:10px;color:var(--text-muted);transition:transform .2s}.toggle-icon.open.svelte-oy8gea{transform:rotate(180deg)}.collapsible-content.svelte-oy8gea{margin-top:var(--space-3)}.btn.svelte-18f749u{font-family:var(--font-sans);font-weight:500;border:none;border-radius:var(--radius-sm);padding:var(--space-2) var(--space-4);cursor:pointer}.primary.svelte-18f749u{background:var(--accent);color:var(--text-on-accent)}.primary.svelte-18f749u:hover{background:var(--accent-hover)}.secondary.svelte-18f749u{background:var(--bg-secondary-btn);color:var(--text-primary)}.secondary.svelte-18f749u:hover{background:var(--btn-secondary-hover)}.danger.svelte-18f749u{background:var(--log-error);color:var(--text-on-accent)}.btn.svelte-18f749u:disabled{background:var(--btn-disabled);cursor:not-allowed}.config-grid.svelte-3p7ghk{display:grid;grid-template-columns:160px 1fr;gap:var(--space-2) var(--space-3);align-items:baseline;margin-bottom:var(--space-4)}.config-label.svelte-3p7ghk{text-transform:uppercase;letter-spacing:.03em;font-size:13px;font-weight:500;color:var(--text-muted)}.config-value.svelte-3p7ghk{color:var(--text-primary)}.config-display.svelte-3p7ghk .collapsible{margin-top:var(--space-4)}.languages-cell.svelte-3p7ghk{display:inline-flex;flex-wrap:wrap;gap:var(--space-2);align-items:center}.lang-badge.svelte-3p7ghk{background:var(--bg-secondary-btn);border-radius:var(--radius-sm);padding:2px var(--space-2);font-family:var(--font-mono);font-size:12px;display:inline-flex;align-items:center;gap:4px}.lang-remove.svelte-3p7ghk{border:none;background:none;cursor:pointer;color:var(--log-error);font-weight:700;line-height:1;padding:0 2px;border-radius:3px}.lang-remove.svelte-3p7ghk:hover{background:var(--bg-secondary-btn)}.tools-grid.svelte-3p7ghk{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:var(--space-2)}.tool-chip.svelte-3p7ghk{background:var(--bg);padding:6px 10px;border-radius:3px;font-family:var(--font-mono);font-size:13px;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.memories-container.svelte-3p7ghk{display:flex;flex-wrap:wrap;gap:var(--space-2)}.memory-item.svelte-3p7ghk{position:relative;display:inline-flex;align-items:center;padding:8px 30px 8px 12px;border-radius:4px;background:var(--bg);border:1px solid var(--border)}.memory-name.svelte-3p7ghk{background:none;border:none;cursor:pointer;font-family:var(--font-mono);font-size:13px;color:var(--text-primary);padding:0}.memory-remove.svelte-3p7ghk{position:absolute;top:2px;right:2px;width:18px;height:18px;display:flex;align-items:center;justify-content:center;background:none;border:none;border-radius:3px;cursor:pointer;color:var(--log-error);font-size:14px;font-weight:700;line-height:1}.memory-remove.svelte-3p7ghk:hover{background:var(--bg-secondary-btn)}.memory-add-btn.svelte-3p7ghk{display:inline-flex;align-items:center;padding:8px 12px;border-radius:4px;border:1px dashed var(--border-strong);background:var(--bg-card);color:var(--text-primary);cursor:pointer;font-family:var(--font-sans);font-size:13px}.memory-add-btn.svelte-3p7ghk:hover{border-color:var(--accent)}.config-footer.svelte-3p7ghk{display:flex;justify-content:space-between;align-items:center;gap:var(--space-3);margin-top:var(--space-4);flex-wrap:wrap}.config-guide-link.svelte-3p7ghk{color:var(--accent);font-weight:500;text-decoration:none}.config-guide-link.svelte-3p7ghk:hover{text-decoration:underline}.bar-row.svelte-vf5ryt{display:grid;grid-template-columns:1fr 3fr auto;gap:var(--space-2);align-items:center;margin:var(--space-1) 0}.bar-name.svelte-vf5ryt{font-family:var(--font-mono);font-size:12px}.bar-track.svelte-vf5ryt{background:var(--bg-secondary-btn);border-radius:var(--radius-sm);height:14px}.bar-fill.svelte-vf5ryt{background:var(--accent);height:100%;border-radius:var(--radius-sm)}.bar-count.svelte-vf5ryt{font-size:12px;color:var(--text-muted)}.no-stats-message.svelte-vf5ryt{color:var(--text-muted)}.card.svelte-xtxwfc{background:var(--bg-card);border:1px solid var(--border);padding:var(--space-6);border-radius:var(--radius);box-shadow:var(--shadow);margin-bottom:var(--space-4)}.card-title.svelte-xtxwfc{text-transform:uppercase;letter-spacing:.04em;font-size:15px;font-weight:600;color:var(--text-muted);margin:0 0 var(--space-4) 0}.list-panel.svelte-1mlfgti{list-style:none;margin:0;padding:0;max-height:340px;overflow-y:auto;display:flex;flex-direction:column;gap:var(--space-1)}.list-panel.svelte-1mlfgti li:where(.svelte-1mlfgti){padding:var(--space-2);border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-elevated);font-family:var(--font-mono);font-size:12px;color:var(--text-primary)}.list-panel.svelte-1mlfgti li.active:where(.svelte-1mlfgti){background:var(--accent);color:var(--text-on-accent);border-color:var(--accent)}.no-stats-message.svelte-1mlfgti{color:var(--text-muted)}.projects.svelte-19jixoo{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--space-2)}.project.svelte-19jixoo{padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);background:var(--bg);border:1px solid var(--border)}.project.active.svelte-19jixoo{background:var(--accent);color:var(--text-on-accent);border-color:var(--accent)}.project-name.svelte-19jixoo{font-weight:700;font-size:13px;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.project-path.svelte-19jixoo{font-family:var(--font-mono);font-size:11px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.project.active.svelte-19jixoo .project-path:where(.svelte-19jixoo){color:var(--text-on-accent);opacity:.85}.no-stats-message.svelte-19jixoo{color:var(--text-muted)}.spinner.svelte-1wjj49t{width:16px;height:16px;border:2px solid var(--border-strong);border-top-color:var(--accent);border-radius:50%;animation:svelte-1wjj49t-spin .7s linear infinite;display:inline-block}@keyframes svelte-1wjj49t-spin{to{transform:rotate(360deg)}}.execution-item.svelte-1wrcimm{display:inline-flex;align-items:center;gap:var(--space-2);border:1px solid var(--border);border-radius:999px;padding:var(--space-1) var(--space-3);margin:2px}.execution-item.running.svelte-1wrcimm{border-color:var(--accent)}.execution-name.svelte-1wrcimm{font-family:var(--font-mono);font-size:12px}.cancel-btn.svelte-1wrcimm{border:none;background:none;cursor:pointer;color:var(--log-error);font-weight:700;line-height:1;padding:0 2px;border-radius:3px}.cancel-btn.svelte-1wrcimm:hover{background:var(--bg-secondary-btn)}.cancel-error.svelte-1wrcimm{color:var(--log-error);margin:var(--space-2) 0 0}.no-stats-message.svelte-1wrcimm{color:var(--text-muted)}.last-exec.svelte-1772y66{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);border:1px solid var(--border);border-radius:var(--radius)}.last-exec.ok.svelte-1772y66{border-color:var(--success);background:color-mix(in srgb,var(--success) 8%,transparent)}.last-exec.fail.svelte-1772y66{border-color:var(--log-error);background:color-mix(in srgb,var(--log-error) 8%,transparent)}.icon.svelte-1772y66{display:inline-flex;width:22px;height:22px;align-items:center;justify-content:center;border-radius:50%;font-size:12px}.ok.svelte-1772y66 .icon:where(.svelte-1772y66){background:color-mix(in srgb,var(--success) 20%,transparent);color:var(--success)}.fail.svelte-1772y66 .icon:where(.svelte-1772y66){background:color-mix(in srgb,var(--log-error) 20%,transparent);color:var(--log-error)}.body.svelte-1772y66{display:flex;flex-direction:column}.status.svelte-1772y66{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted)}.execution-name.svelte-1772y66{font-family:var(--font-mono);font-size:13px;color:var(--text-primary)}.meta.svelte-1772y66{margin-left:auto;font-family:var(--font-mono);font-size:12px;color:var(--text-muted)}.no-stats-message.svelte-1772y66{color:var(--text-muted)}.cancelled-item.svelte-1re8sys{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-1) 0;font-family:var(--font-mono);font-size:12px;color:var(--text-secondary)}.icon.svelte-1re8sys{display:inline-flex;width:16px;height:16px;align-items:center;justify-content:center;border-radius:50%;background:var(--bg-secondary-btn);color:var(--log-error);font-size:10px}.cancelled-item.abandoned.svelte-1re8sys .icon:where(.svelte-1re8sys){color:var(--log-warning)}.meta.svelte-1re8sys{margin-left:auto;color:var(--text-muted)}.news-item.svelte-1yvzfte{border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-3);margin-bottom:var(--space-3);background:var(--bg-card);box-shadow:var(--shadow)}.news-dismiss.svelte-1yvzfte{margin-top:var(--space-2);background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);padding:var(--space-1) var(--space-3);cursor:pointer;color:var(--text-primary)}.grid.svelte-da5j7n{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--space-3);margin-bottom:var(--space-3)}.card.svelte-da5j7n{padding:var(--space-3);background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow)}.title.svelte-da5j7n{color:var(--text-muted);font-size:.75rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;margin-bottom:var(--space-1)}.main.svelte-da5j7n{font-family:var(--font-mono);font-size:1.4rem;font-weight:700;color:var(--text-primary);line-height:1.1}.sub.svelte-da5j7n{color:var(--text-muted);font-size:.8em;margin-top:var(--space-1)}@media(max-width:800px){.grid.svelte-da5j7n{grid-template-columns:repeat(2,minmax(0,1fr))}}.root.svelte-torfu5{position:relative;display:inline-block}.trigger.svelte-torfu5{display:inline-flex;align-items:center;gap:var(--space-2);padding:var(--space-1) var(--space-2);border:1px solid var(--border);background:var(--bg-card);color:var(--text-primary);border-radius:var(--radius);cursor:pointer;font-family:inherit}.root.active.svelte-torfu5 .trigger:where(.svelte-torfu5){border-color:var(--accent)}.clear.svelte-torfu5{color:var(--text-muted);cursor:pointer;-webkit-user-select:none;user-select:none}.clear.svelte-torfu5:hover{color:var(--accent)}.chev.svelte-torfu5{color:var(--text-muted)}.panel.svelte-torfu5{position:absolute;top:calc(100% + 4px);left:0;min-width:200px;background:var(--bg-card);border:1px solid var(--border-strong);border-radius:var(--radius);box-shadow:var(--shadow);z-index:50}.filter-input.svelte-torfu5{width:100%;padding:var(--space-2);border:none;border-bottom:1px solid var(--border);background:transparent;color:var(--text-primary);font-family:inherit;outline:none}.filter-input.svelte-torfu5:focus{border-bottom-color:var(--accent)}.list.svelte-torfu5{list-style:none;margin:0;padding:var(--space-1) 0;max-height:240px;overflow-y:auto}.list.svelte-torfu5 li:where(.svelte-torfu5){display:flex;gap:var(--space-2);padding:var(--space-1) var(--space-2);cursor:pointer}.list.svelte-torfu5 li.highlight:where(.svelte-torfu5){background:var(--bg-secondary-btn)}.check.svelte-torfu5{width:1em;color:var(--accent)}.empty.svelte-torfu5{color:var(--text-muted);padding:var(--space-2)}.row.svelte-1112a67{border-bottom:1px solid var(--border);list-style:none}.head.svelte-1112a67{width:100%;display:grid;grid-template-columns:24px 110px 1fr 70px 50px;gap:var(--space-2);align-items:center;background:transparent;border:0;color:var(--text-primary);cursor:pointer;padding:var(--space-1) var(--space-2);text-align:left;font-family:var(--font-mono);font-size:.85em}.head.svelte-1112a67:hover{background:var(--bg-secondary-btn)}.chev.svelte-1112a67,.time.svelte-1112a67{color:var(--text-muted)}.tool.svelte-1112a67{color:var(--text-primary)}.duration.svelte-1112a67{color:var(--text-muted)}.status.ok.svelte-1112a67{color:var(--success)}.status.err.svelte-1112a67{color:var(--log-error)}.detail.svelte-1112a67{padding:var(--space-2) var(--space-3) var(--space-3) calc(24px + var(--space-2));font-family:var(--font-mono);font-size:.85em;color:var(--text-primary)}.k.svelte-1112a67{color:var(--text-muted);margin-right:var(--space-1)}.block.svelte-1112a67{margin-top:var(--space-2)}.code.svelte-1112a67{background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);padding:var(--space-2);white-space:pre-wrap;word-break:break-all;margin:var(--space-1) 0}.link.svelte-1112a67{background:transparent;border:0;color:var(--accent);cursor:pointer;padding:0}.note.svelte-1112a67{color:var(--text-muted);font-size:.85em}.err.svelte-1112a67{color:var(--log-error)}.card.svelte-1c7fjwn{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-3);box-shadow:var(--shadow);margin-bottom:var(--space-3)}.head.svelte-1c7fjwn{display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-2)}.title.svelte-1c7fjwn{margin:0;font-size:1em}.controls.svelte-1c7fjwn{margin-left:auto;display:flex;gap:var(--space-2)}.ctrl.svelte-1c7fjwn{background:transparent;border:1px solid var(--border);color:var(--text-primary);border-radius:var(--radius-sm);padding:var(--space-1) var(--space-2);cursor:pointer;font-family:inherit}.ctrl.svelte-1c7fjwn:hover{background:var(--bg-secondary-btn)}.pager.svelte-1c7fjwn{display:flex;gap:var(--space-2);align-items:center;padding:var(--space-2) 0;border-bottom:1px solid var(--border)}.pager.svelte-1c7fjwn button:where(.svelte-1c7fjwn){background:transparent;border:1px solid var(--border);color:var(--text-primary);border-radius:var(--radius-sm);padding:var(--space-1) var(--space-2);cursor:pointer}.pager.svelte-1c7fjwn button:where(.svelte-1c7fjwn):disabled{opacity:.4;cursor:not-allowed}.grow.svelte-1c7fjwn{flex:1}.pageinfo.svelte-1c7fjwn{color:var(--text-muted);font-size:.9em}.list.svelte-1c7fjwn{list-style:none;margin:0;padding:0}.empty.svelte-1c7fjwn{color:var(--text-muted);padding:var(--space-3);text-align:center}.banner.svelte-1c7fjwn{background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);padding:var(--space-1) var(--space-2);color:var(--text-muted);margin-bottom:var(--space-2)}.banner.error.svelte-1c7fjwn{border-color:var(--log-error);color:var(--log-error)}.overview-container.svelte-1im2p0o{display:grid;grid-template-columns:minmax(0,1fr) 400px;gap:var(--space-6)}@media(max-width:1000px){.overview-container.svelte-1im2p0o{grid-template-columns:1fr}}.log-action-buttons.svelte-pdgm8l{display:flex;gap:var(--space-2);justify-content:flex-end;margin-bottom:var(--space-2)}.log-action-btn.svelte-pdgm8l{background:var(--bg-secondary-btn);border:1px solid var(--border);border-radius:var(--radius-sm);padding:var(--space-1) var(--space-3);cursor:pointer;color:var(--text-primary)}.log-action-btn.svelte-pdgm8l:disabled{color:var(--btn-disabled);cursor:not-allowed}.danger.svelte-pdgm8l:not(:disabled){color:var(--log-error)}.log-container.svelte-1f7p4v8{height:calc(100vh - 220px);overflow:auto;font-family:var(--font-mono);font-size:12px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-3);box-shadow:var(--shadow)}.log-line.svelte-1f7p4v8{white-space:pre-wrap}.debug.svelte-1f7p4v8{color:var(--log-debug)}.info.svelte-1f7p4v8{color:var(--log-info)}.warning.svelte-1f7p4v8{color:var(--log-warning)}.error.svelte-1f7p4v8{color:var(--log-error)}.log-line .tool-name{background-color:var(--tool-highlight);color:var(--tool-highlight-text);font-weight:700}.chart-group.svelte-1tmlu4m{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-4);box-shadow:var(--shadow)}.canvas-wrap.svelte-1tmlu4m{position:relative}.kpi-strip.svelte-ebpvur{display:flex;flex-wrap:wrap;gap:var(--space-3);margin-bottom:var(--space-3)}.kpi.svelte-ebpvur{flex:1 1 0;min-width:140px;display:flex;flex-direction:column;gap:var(--space-1);padding:var(--space-3) var(--space-4);background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow)}.kpi-total.svelte-ebpvur{border-color:var(--accent)}.kpi-label.svelte-ebpvur{font-size:.75rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--text-muted)}.kpi-value.svelte-ebpvur{font-family:var(--font-mono);font-size:1.4rem;font-weight:700;color:var(--text-primary);line-height:1.1}.kpi-total.svelte-ebpvur .kpi-value:where(.svelte-ebpvur){color:var(--accent)}.root.svelte-5b28k3{display:inline-flex;gap:var(--space-2);align-items:center;color:var(--text-secondary)}select.svelte-5b28k3{background:var(--bg-elevated);border:1px solid var(--border);border-radius:var(--radius);color:var(--text-primary);padding:var(--space-1) var(--space-2)}.card.svelte-wggcdu{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-3);box-shadow:var(--shadow)}.head.svelte-wggcdu{display:flex;align-items:center;margin-bottom:var(--space-2)}.head.svelte-wggcdu h3:where(.svelte-wggcdu){margin:0;font-size:1em;color:var(--text-primary)}.controls.svelte-wggcdu{margin-left:auto;display:flex;gap:var(--space-2);align-items:center;color:var(--text-secondary)}.controls.svelte-wggcdu select:where(.svelte-wggcdu),.controls.svelte-wggcdu button:where(.svelte-wggcdu){background:var(--bg-elevated);border:1px solid var(--border);border-radius:var(--radius);color:var(--text-primary);padding:var(--space-1) var(--space-2);cursor:pointer}.hint.svelte-wggcdu{color:var(--text-muted);font-size:.85em;margin:var(--space-1) 0 0}.panel.svelte-1kpn8jr{position:fixed;top:0;right:0;bottom:0;width:340px;background:var(--bg-elevated);border-left:1px solid var(--border-strong);box-shadow:var(--shadow-elevated);padding:var(--space-3);overflow-y:auto;z-index:40}header.svelte-1kpn8jr{display:flex;align-items:center;margin-bottom:var(--space-2)}header.svelte-1kpn8jr h3:where(.svelte-1kpn8jr){margin:0;font-family:var(--font-mono);color:var(--text-primary)}.close.svelte-1kpn8jr{margin-left:auto;background:transparent;border:0;color:var(--text-secondary);font-size:1.4em;cursor:pointer}.close.svelte-1kpn8jr:hover{color:var(--accent)}.grid.svelte-1kpn8jr{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-1) var(--space-2);margin:0}dt.svelte-1kpn8jr{color:var(--text-secondary)}dd.svelte-1kpn8jr{margin:0;font-weight:600;color:var(--text-primary)}.hint.svelte-1kpn8jr{color:var(--text-muted);font-size:.85em}h4.svelte-1kpn8jr{margin:var(--space-3) 0 var(--space-1);font-size:.95em;color:var(--text-primary)}.errors.svelte-1kpn8jr,.calls.svelte-1kpn8jr{list-style:none;margin:0;padding:0;font-family:var(--font-mono);font-size:.85em;color:var(--text-primary)}.calls.svelte-1kpn8jr li:where(.svelte-1kpn8jr){display:grid;grid-template-columns:1fr auto auto;gap:var(--space-2)}.ok.svelte-1kpn8jr{color:var(--success)}.err.svelte-1kpn8jr{color:var(--log-error)}.empty.svelte-1kpn8jr{color:var(--text-muted)}.open.svelte-1kpn8jr{margin-top:var(--space-3);background:var(--accent);color:var(--text-on-accent);border:0;border-radius:var(--radius);padding:var(--space-2) var(--space-3);cursor:pointer}.open.svelte-1kpn8jr:hover{background:var(--accent-hover)}.controls.svelte-af288w{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-2) var(--space-3);margin-bottom:var(--space-4);background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow)}.spacer.svelte-af288w{flex:1}.charts-grid.svelte-af288w{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-4);margin-top:var(--space-4)}.charts-bars.svelte-af288w{display:grid;grid-template-columns:1fr;gap:var(--space-4);margin-top:var(--space-4)}@media(max-width:1000px){.charts-grid.svelte-af288w{grid-template-columns:1fr}}.estimator-name.svelte-af288w{color:var(--text-muted);margin:var(--space-2) 0}.no-stats-message.svelte-af288w{color:var(--text-muted)}.list.svelte-1pcafu8{list-style:none;margin:0;padding:0}.row.svelte-1pcafu8{display:flex;align-items:center;gap:var(--space-1);width:100%;text-align:left;background:transparent;border:0;color:var(--text-primary);cursor:pointer;padding:var(--space-1);padding-left:calc(var(--space-2) + var(--depth, 0) * var(--space-3));font-family:var(--font-mono);font-size:.9em}.row.svelte-1pcafu8:hover{background:var(--bg)}.row.selected.svelte-1pcafu8{background:var(--bg);color:var(--accent)}.chev.svelte-1pcafu8{width:1em;color:var(--text-secondary)}.loading.svelte-1pcafu8{color:var(--text-secondary);padding-left:calc(var(--space-2) + var(--depth, 0) * var(--space-3))}.error-row.svelte-1pcafu8{display:flex;align-items:center;gap:var(--space-1);padding:var(--space-1);padding-left:calc(var(--space-2) + var(--depth, 0) * var(--space-3));color:var(--log-error);font-family:var(--font-mono);font-size:.85em}.err-msg.svelte-1pcafu8{color:var(--log-error);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.warn.svelte-1pcafu8{color:var(--log-error);margin-left:var(--space-1)}.root.svelte-vdq9pf{height:100%;overflow-y:auto}.empty.svelte-vdq9pf{color:var(--text-secondary);padding:var(--space-3)}.list.svelte-vdq9pf{list-style:none;margin:0;padding:0;font-family:var(--font-mono);font-size:.85em}.row.svelte-vdq9pf{display:grid;grid-template-columns:80px 1fr 60px;gap:var(--space-2);padding:var(--space-1) var(--space-2);padding-left:calc(var(--space-2) + var(--depth, 0) * var(--space-3));border-bottom:1px solid var(--border)}.kind.svelte-vdq9pf{color:var(--text-secondary)}.loc.svelte-vdq9pf{color:var(--text-secondary);text-align:right}.error-card.svelte-vdq9pf{background:var(--bg);border:1px solid var(--log-error);border-radius:var(--radius);padding:var(--space-2);color:var(--log-error);margin:var(--space-2);display:flex;flex-direction:column;gap:var(--space-2)}.error-card.svelte-vdq9pf .msg:where(.svelte-vdq9pf){font-family:var(--font-mono);font-size:.85em;color:var(--text-primary)}.retry.svelte-vdq9pf{align-self:flex-start;background:var(--bg-elevated);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-1) var(--space-2);color:var(--text-primary);cursor:pointer}.root.svelte-1c5pbf5{display:flex;flex-direction:column;height:100%;padding:var(--space-2)}.input.svelte-1c5pbf5{width:100%;padding:var(--space-2);background:var(--bg-elevated);border:1px solid var(--border);border-radius:var(--radius);color:var(--text-primary);font-family:inherit}.input.svelte-1c5pbf5:focus{outline:none;border-color:var(--accent)}.status.svelte-1c5pbf5{color:var(--text-secondary);padding:var(--space-2)}.error-banner.svelte-1c5pbf5{margin-top:var(--space-2);background:var(--bg);border:1px solid var(--log-error);border-radius:var(--radius);padding:var(--space-2);color:var(--log-error);display:flex;flex-direction:column;gap:var(--space-1)}.error-banner.svelte-1c5pbf5 .msg:where(.svelte-1c5pbf5){color:var(--text-primary);font-family:var(--font-mono);font-size:.85em}.results.svelte-1c5pbf5{list-style:none;margin:var(--space-2) 0 0;padding:0;overflow-y:auto;flex:1}.match.svelte-1c5pbf5{width:100%;text-align:left;background:transparent;border:0;border-bottom:1px solid var(--border);padding:var(--space-1) var(--space-2);color:var(--text-primary);font-family:var(--font-mono);font-size:.85em;cursor:pointer;display:grid;grid-template-columns:80px 1fr auto;gap:var(--space-2)}.match.svelte-1c5pbf5:hover{background:var(--bg)}.kind.svelte-1c5pbf5,.path.svelte-1c5pbf5{color:var(--text-secondary)}.root.svelte-umravk{height:100%;overflow-y:auto;padding:var(--space-2)}header.svelte-umravk{display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-2)}header.svelte-umravk h3:where(.svelte-umravk){margin:0;font-size:1em}.refresh.svelte-umravk{margin-left:auto;background:var(--bg-elevated);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-1) var(--space-2);cursor:pointer;color:var(--text-primary)}.refresh.svelte-umravk:disabled{opacity:.6;cursor:progress}.warn.svelte-umravk{background:var(--bg);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:var(--radius);padding:var(--space-2);color:var(--text-secondary);margin-bottom:var(--space-2)}.error-card.svelte-umravk{background:var(--bg);border:1px solid var(--log-error);border-radius:var(--radius);padding:var(--space-2);color:var(--log-error);margin-bottom:var(--space-2)}.error-card.svelte-umravk .msg:where(.svelte-umravk){color:var(--text-primary);font-family:var(--font-mono);font-size:.85em}.empty.svelte-umravk{color:var(--text-secondary);padding:var(--space-2)}.file.svelte-umravk{border-bottom:1px solid var(--border);padding:var(--space-1) 0}.file.svelte-umravk summary:where(.svelte-umravk){display:flex;cursor:pointer;font-family:var(--font-mono);font-size:.9em}.file.svelte-umravk summary:where(.svelte-umravk) .count:where(.svelte-umravk){margin-left:auto;color:var(--text-secondary)}.diags.svelte-umravk{list-style:none;margin:var(--space-1) 0 0;padding:0;font-family:var(--font-mono);font-size:.85em}.diags.svelte-umravk li:where(.svelte-umravk){display:grid;grid-template-columns:70px 60px 1fr auto;gap:var(--space-2);padding:var(--space-1) var(--space-2)}.sev-error.svelte-umravk{color:var(--log-error)}.sev-warning.svelte-umravk{color:var(--log-warning)}.sev-info.svelte-umravk,.sev-hint.svelte-umravk,.source.svelte-umravk{color:var(--text-secondary)}.layout.svelte-z2f184{display:grid;grid-template-columns:260px 1fr 320px;gap:var(--space-2);height:calc(100vh - 140px)}.tree.svelte-z2f184,.middle.svelte-z2f184,.diagnostics.svelte-z2f184{background:var(--bg-elevated);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;display:flex;flex-direction:column}.middle-tabs.svelte-z2f184{display:flex;border-bottom:1px solid var(--border)}.middle-tabs.svelte-z2f184 button:where(.svelte-z2f184){background:transparent;border:0;padding:var(--space-2);color:var(--text-secondary);cursor:pointer}.middle-tabs.svelte-z2f184 button.active:where(.svelte-z2f184){color:var(--accent);border-bottom:2px solid var(--accent)}.tree.svelte-z2f184{overflow-y:auto}@media(max-width:1000px){.layout.svelte-z2f184{grid-template-columns:1fr;grid-template-rows:auto auto auto;height:auto}}.backdrop.svelte-19jhfg1{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000073;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:center;justify-content:center;z-index:1000}.modal-content.svelte-19jhfg1{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-6);max-width:600px;width:90%;box-shadow:var(--shadow-elevated);position:relative}.modal-error.svelte-19jhfg1{color:var(--log-error);margin:0 0 var(--space-3)}.modal-close.svelte-19jhfg1{position:absolute;top:var(--space-3);right:var(--space-4);cursor:pointer;font-size:22px;color:var(--text-muted);background:none;border:none;line-height:1;padding:0}.combobox.svelte-hol9vh{position:relative}input.svelte-hol9vh{width:100%;padding:var(--space-2);border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-card);color:var(--text-primary);font-family:var(--font-sans)}.combobox-caret.svelte-hol9vh{position:absolute;right:var(--space-2);top:var(--space-2);pointer-events:none}.combobox-options.svelte-hol9vh{list-style:none;margin:0;padding:0;position:absolute;z-index:5;width:100%;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-sm);max-height:200px;overflow:auto}.combobox-options.svelte-hol9vh li:where(.svelte-hol9vh){padding:var(--space-2);cursor:pointer}.combobox-options.svelte-hol9vh li:where(.svelte-hol9vh):hover{background:var(--bg-secondary-btn)}.combobox-empty.svelte-hol9vh{padding:var(--space-2);color:var(--text-muted)}.memory-rename-input.svelte-blcc47{font-family:var(--font-mono);font-size:14px;padding:var(--space-1) var(--space-2);border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-card);color:var(--text-primary)}.memory-name-display.svelte-blcc47{font-family:var(--font-mono)}.rename-trigger.svelte-blcc47{background:none;border:none;cursor:pointer;color:var(--text-muted)}.main.svelte-1n46o8q{max-width:var(--max-width);margin:0 auto;padding:var(--space-6)} diff --git a/src/serena/resources/dashboard/assets/index-vCsKzPK1.js b/src/serena/resources/dashboard/assets/index-vCsKzPK1.js new file mode 100644 index 000000000..d157b7db7 --- /dev/null +++ b/src/serena/resources/dashboard/assets/index-vCsKzPK1.js @@ -0,0 +1,31 @@ +var sh=Object.defineProperty;var Qa=e=>{throw TypeError(e)};var rh=(e,t,n)=>t in e?sh(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var z=(e,t,n)=>rh(e,typeof t!="symbol"?t+"":t,n),ho=(e,t,n)=>t.has(e)||Qa("Cannot "+n);var M=(e,t,n)=>(ho(e,t,"read from private field"),n?n.call(e):t.get(e)),J=(e,t,n)=>t.has(e)?Qa("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n),nt=(e,t,n,i)=>(ho(e,t,"write to private field"),i?i.call(e,n):t.set(e,n),n),lt=(e,t,n)=>(ho(e,t,"access private method"),n);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))i(s);new MutationObserver(s=>{for(const r of s)if(r.type==="childList")for(const o of r.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&i(o)}).observe(document,{childList:!0,subtree:!0});function n(s){const r={};return s.integrity&&(r.integrity=s.integrity),s.referrerPolicy&&(r.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?r.credentials="include":s.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function i(s){if(s.ep)return;s.ep=!0;const r=n(s);fetch(s.href,r)}})();const qc=!1;var xa=Array.isArray,oh=Array.prototype.indexOf,ni=Array.prototype.includes,Kr=Array.from,ah=Object.defineProperty,us=Object.getOwnPropertyDescriptor,Yc=Object.getOwnPropertyDescriptors,lh=Object.prototype,ch=Array.prototype,ya=Object.getPrototypeOf,$a=Object.isExtensible;const ws=()=>{};function uh(e){return e()}function jo(e){for(var t=0;t{e=i,t=s});return{promise:n,resolve:e,reject:t}}function Xc(e,t){if(Array.isArray(e))return e;if(!(Symbol.iterator in e))return Array.from(e);const n=[];for(const i of e)if(n.push(i),n.length===t)break;return n}const qt=2,Ri=4,zs=8,Kc=1<<24,Ae=16,Re=32,Ln=64,No=128,_e=512,Nt=1024,Vt=2048,qe=4096,Jt=8192,Ee=16384,gi=32768,Bo=1<<25,li=65536,Tr=1<<17,dh=1<<18,Ui=1<<19,Gc=1<<20,We=1<<25,ci=65536,Dr=1<<21,Mi=1<<22,An=1<<23,Tn=Symbol("$state"),hh=Symbol(""),gr=Symbol("attributes"),Vo=Symbol("class"),Wo=Symbol("style"),is=Symbol("text"),pr=Symbol("form reset"),Gr=new class extends Error{constructor(){super(...arguments);z(this,"name","StaleReactionError");z(this,"message","The reaction that called `getAbortSignal()` was re-run or destroyed")}};var Vc;const fh=!!((Vc=globalThis.document)!=null&&Vc.contentType)&&globalThis.document.contentType.includes("xml");function Zc(e){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function gh(){throw new Error("https://svelte.dev/e/async_derived_orphan")}function ph(e,t,n){throw new Error("https://svelte.dev/e/each_key_duplicate")}function vh(e){throw new Error("https://svelte.dev/e/effect_in_teardown")}function mh(){throw new Error("https://svelte.dev/e/effect_in_unowned_derived")}function _h(e){throw new Error("https://svelte.dev/e/effect_orphan")}function bh(){throw new Error("https://svelte.dev/e/effect_update_depth_exceeded")}function xh(){throw new Error("https://svelte.dev/e/state_descriptors_fixed")}function yh(){throw new Error("https://svelte.dev/e/state_prototype_fixed")}function wh(){throw new Error("https://svelte.dev/e/state_unsafe_mutation")}function kh(){throw new Error("https://svelte.dev/e/svelte_boundary_reset_onerror")}const Sh=1,Mh=2,Jc=4,Ph=8,Ch=16,Ah=1,Th=2,jt=Symbol("uninitialized"),Qc="http://www.w3.org/1999/xhtml",Dh="http://www.w3.org/2000/svg",Eh="http://www.w3.org/1998/Math/MathML";function Oh(){console.warn("https://svelte.dev/e/derived_inert")}function Lh(){console.warn("https://svelte.dev/e/select_multiple_invalid_value")}function Rh(){console.warn("https://svelte.dev/e/svelte_boundary_reset_noop")}function $c(e){return e===this.v}function Ih(e,t){return e!=e?t==t:e!==t||e!==null&&typeof e=="object"||typeof e=="function"}function tu(e){return!Ih(e,this.v)}let js=!1,Fh=!1;function zh(){js=!0}let Pt=null;function Ii(e){Pt=e}function tt(e,t=!1,n){Pt={p:Pt,i:!1,c:null,e:null,s:e,x:null,r:at,l:js&&!t?{s:null,u:null,$:[]}:null}}function et(e){var t=Pt,n=t.e;if(n!==null){t.e=null;for(var i of n)ku(i)}return t.i=!0,Pt=t.p,{}}function Xi(){return!js||Pt!==null&&Pt.l===null}let Un=[];function eu(){var e=Un;Un=[],jo(e)}function Dn(e){if(Un.length===0&&!hs){var t=Un;queueMicrotask(()=>{t===Un&&eu()})}Un.push(e)}function jh(){for(;Un.length>0;)eu()}function nu(e){var t=at;if(t===null)return rt.f|=An,e;if((t.f&gi)===0&&(t.f&Ri)===0)throw e;xn(e,t)}function xn(e,t){for(;t!==null;){if((t.f&No)!==0){if((t.f&gi)===0)throw e;try{t.b.error(e);return}catch(n){e=n}}t=t.parent}throw e}const Nh=-7169;function Ct(e,t){e.f=e.f&Nh|t}function wa(e){(e.f&_e)!==0||e.deps===null?Ct(e,Nt):Ct(e,qe)}function iu(e){if(e!==null)for(const t of e)(t.f&qt)===0||(t.f&ci)===0||(t.f^=ci,iu(t.deps))}function su(e,t,n){(e.f&Vt)!==0?t.add(e):(e.f&qe)!==0&&n.add(e),iu(e.deps),Ct(e,Nt)}let fo=null,_i=null,Z=null,ds=null,Ht=null,Ho=null,hs=!1,go=!1,wi=null,vr=null;var tl=0;let Bh=1;var Ci,vn,Zn,Ai,Ti,Jn,Di,tn,Es,ce,Os,mn,je,Ne,Ei,Qn,ht,qo,ss,Yo,ru,ou,mr,Vh,Uo,yi;const Hr=class Hr{constructor(){J(this,ht);z(this,"id",Bh++);J(this,Ci,!1);z(this,"linked",!0);J(this,vn,null);J(this,Zn,null);z(this,"async_deriveds",new Map);z(this,"current",new Map);z(this,"previous",new Map);z(this,"unblocked",new Set);J(this,Ai,new Set);J(this,Ti,new Set);J(this,Jn,new Set);J(this,Di,0);J(this,tn,new Map);J(this,Es,null);J(this,ce,[]);J(this,Os,[]);J(this,mn,new Set);J(this,je,new Set);J(this,Ne,new Map);J(this,Ei,new Set);z(this,"is_fork",!1);J(this,Qn,!1)}skip_effect(t){M(this,Ne).has(t)||M(this,Ne).set(t,{d:[],m:[]}),M(this,Ei).delete(t)}unskip_effect(t,n=i=>this.schedule(i)){var i=M(this,Ne).get(t);if(i){M(this,Ne).delete(t);for(var s of i.d)Ct(s,Vt),n(s);for(s of i.m)Ct(s,qe),n(s)}M(this,Ei).add(t)}capture(t,n,i=!1){t.v!==jt&&!this.previous.has(t)&&this.previous.set(t,t.v),(t.f&An)===0&&(this.current.set(t,[n,i]),Ht==null||Ht.set(t,n)),this.is_fork||(t.v=n)}activate(){Z=this}deactivate(){Z=null,Ht=null}flush(){try{go=!0,Z=this,lt(this,ht,ss).call(this)}finally{tl=0,Ho=null,wi=null,vr=null,go=!1,Z=null,Ht=null,ii.clear()}}discard(){for(const t of M(this,Ti))t(this);M(this,Ti).clear(),M(this,Jn).clear(),lt(this,ht,yi).call(this)}register_created_effect(t){M(this,Os).push(t)}increment(t,n){if(nt(this,Di,M(this,Di)+1),t){let i=M(this,tn).get(n)??0;M(this,tn).set(n,i+1)}}decrement(t,n){if(nt(this,Di,M(this,Di)-1),t){let i=M(this,tn).get(n)??0;i===1?M(this,tn).delete(n):M(this,tn).set(n,i-1)}M(this,Qn)||(nt(this,Qn,!0),Dn(()=>{nt(this,Qn,!1),this.linked&&this.flush()}))}transfer_effects(t,n){for(const i of t)M(this,mn).add(i);for(const i of n)M(this,je).add(i);t.clear(),n.clear()}oncommit(t){M(this,Ai).add(t)}ondiscard(t){M(this,Ti).add(t)}on_fork_commit(t){M(this,Jn).add(t)}run_fork_commit_callbacks(){for(const t of M(this,Jn))t(this);M(this,Jn).clear()}settled(){return(M(this,Es)??nt(this,Es,Uc())).promise}static ensure(){var t;if(Z===null){const n=Z=new Hr;lt(t=n,ht,Uo).call(t),!go&&!hs&&Dn(()=>{M(n,Ci)||n.flush()})}return Z}apply(){{Ht=null;return}}schedule(t){var s;if(Ho=t,(s=t.b)!=null&&s.is_pending&&(t.f&(Ri|zs|Kc))!==0&&(t.f&gi)===0){t.b.defer_effect(t);return}for(var n=t;n.parent!==null;){n=n.parent;var i=n.f;if(wi!==null&&n===at&&(rt===null||(rt.f&qt)===0))return;if((i&(Ln|Re))!==0){if((i&Nt)===0)return;n.f^=Nt}}M(this,ce).push(n)}};Ci=new WeakMap,vn=new WeakMap,Zn=new WeakMap,Ai=new WeakMap,Ti=new WeakMap,Jn=new WeakMap,Di=new WeakMap,tn=new WeakMap,Es=new WeakMap,ce=new WeakMap,Os=new WeakMap,mn=new WeakMap,je=new WeakMap,Ne=new WeakMap,Ei=new WeakMap,Qn=new WeakMap,ht=new WeakSet,qo=function(){if(this.is_fork)return!0;for(const i of M(this,tn).keys()){for(var t=i,n=!1;t.parent!==null;){if(M(this,Ne).has(t)){n=!0;break}t=t.parent}if(!n)return!0}return!1},ss=function(){var l,c,u,d;if(nt(this,Ci,!0),tl++>1e3&&(lt(this,ht,yi).call(this),Hh()),!lt(this,ht,qo).call(this)){for(const h of M(this,mn))M(this,je).delete(h),Ct(h,Vt),this.schedule(h);for(const h of M(this,je))Ct(h,qe),this.schedule(h)}const t=M(this,ce);nt(this,ce,[]),this.apply();var n=wi=[],i=[],s=vr=[];for(const h of t)try{lt(this,ht,Yo).call(this,h,n,i)}catch(f){throw cu(h),f}if(Z=null,s.length>0){var r=Hr.ensure();for(const h of s)r.schedule(h)}if(wi=null,vr=null,lt(this,ht,qo).call(this)){lt(this,ht,mr).call(this,i),lt(this,ht,mr).call(this,n);for(const[h,f]of M(this,Ne))lu(h,f);s.length>0&<(l=Z,ht,ss).call(l);return}const o=lt(this,ht,ru).call(this);if(o){lt(c=o,ht,ou).call(c,this);return}M(this,mn).clear(),M(this,je).clear();for(const h of M(this,Ai))h(this);M(this,Ai).clear(),ds=this,el(i),el(n),ds=null,(u=M(this,Es))==null||u.resolve();var a=Z;if(this.linked&&M(this,Di)===0&<(this,ht,yi).call(this),M(this,ce).length>0){a===null&&(a=this,lt(this,ht,Uo).call(this));const h=a;M(h,ce).push(...M(this,ce).filter(f=>!M(h,ce).includes(f)))}a!==null&<(d=a,ht,ss).call(d)},Yo=function(t,n,i){t.f^=Nt;for(var s=t.first;s!==null;){var r=s.f,o=(r&(Re|Ln))!==0,a=o&&(r&Nt)!==0,l=a||(r&Jt)!==0||M(this,Ne).has(s);if(!l&&s.fn!==null){o?s.f^=Nt:(r&Ri)!==0?n.push(s):Vs(s)&&((r&Ae)!==0&&M(this,je).add(s),zi(s));var c=s.first;if(c!==null){s=c;continue}}for(;s!==null;){var u=s.next;if(u!==null){s=u;break}s=s.parent}}},ru=function(){for(var t=M(this,vn);t!==null;){if(!t.is_fork){for(const[n,[,i]]of this.current)if(t.current.has(n)&&!i)return t}t=M(t,vn)}return null},ou=function(t){var i;for(const[s,r]of t.current)!this.previous.has(s)&&t.previous.has(s)&&this.previous.set(s,t.previous.get(s)),this.current.set(s,r);for(const[s,r]of t.async_deriveds){const o=this.async_deriveds.get(s);o&&r.promise.then(o.resolve)}const n=s=>{var r=s.reactions;if(r!==null)for(const l of r){var o=l.f;if((o&qt)!==0)n(l);else{var a=l;o&(Mi|Ae)&&!this.async_deriveds.has(a)&&(M(this,je).delete(a),Ct(a,Vt),this.schedule(a))}}};for(const s of this.current.keys())n(s);this.oncommit(()=>t.discard()),lt(i=t,ht,yi).call(i),Z=this,lt(this,ht,ss).call(this)},mr=function(t){for(var n=0;n!this.current.has(h));if(s.length===0)t&&d.discard();else if(n.length>0){if(t)for(const h of M(this,Ei))d.unskip_effect(h,f=>{var g;(f.f&(Ae|Mi))!==0?d.schedule(f):lt(g=d,ht,mr).call(g,[f])});d.activate();var r=new Set,o=new Map;for(var a of n)au(a,s,r,o);o=new Map;var l=[...d.current.keys()].filter(h=>this.current.has(h)?this.current.get(h)[0]!==h.v:!0);if(l.length>0)for(const h of M(this,Os))(h.f&(Ee|Jt|Tr))===0&&ka(h,l,o)&&((h.f&(Mi|Ae))!==0?(Ct(h,Vt),d.schedule(h)):M(d,mn).add(h));if(M(d,ce).length>0&&!M(d,Qn)){d.apply();for(var c of M(d,ce))lt(u=d,ht,Yo).call(u,c,[],[]);nt(d,ce,[])}d.deactivate()}}}},Uo=function(){_i===null?fo=_i=this:(nt(_i,Zn,this),nt(this,vn,_i)),_i=this},yi=function(){var t=M(this,vn),n=M(this,Zn);t===null?fo=n:nt(t,Zn,n),n===null?_i=t:nt(n,vn,t),this.linked=!1};let ui=Hr;function Wh(e){var t=hs;hs=!0;try{for(var n;;){if(jh(),Z===null)return n;Z.flush()}}finally{hs=t}}function Hh(){try{bh()}catch(e){xn(e,Ho)}}let Se=null;function el(e){var t=e.length;if(t!==0){for(var n=0;n0)){ii.clear();for(const s of Se){if((s.f&(Ee|Jt))!==0)continue;const r=[s];let o=s.parent;for(;o!==null;)Se.has(o)&&(Se.delete(o),r.push(o)),o=o.parent;for(let a=r.length-1;a>=0;a--){const l=r[a];(l.f&(Ee|Jt))===0&&zi(l)}}Se.clear()}}Se=null}}function au(e,t,n,i){if(!n.has(e)&&(n.add(e),e.reactions!==null))for(const s of e.reactions){const r=s.f;(r&qt)!==0?au(s,t,n,i):(r&(Mi|Ae))!==0&&(r&Vt)===0&&ka(s,t,i)&&(Ct(s,Vt),Sa(s))}}function ka(e,t,n){const i=n.get(e);if(i!==void 0)return i;if(e.deps!==null)for(const s of e.deps){if(ni.call(t,s))return!0;if((s.f&qt)!==0&&ka(s,t,n))return n.set(s,!0),!0}return n.set(e,!1),!1}function Sa(e){Z.schedule(e)}function lu(e,t){if(!((e.f&Re)!==0&&(e.f&Nt)!==0)){(e.f&Vt)!==0?t.d.push(e):(e.f&qe)!==0&&t.m.push(e),Ct(e,Nt);for(var n=e.first;n!==null;)lu(n,t),n=n.next}}function cu(e){Ct(e,Nt);for(var t=e.first;t!==null;)cu(t),t=t.next}function qh(e){let t=0,n=Rn(0),i;return()=>{Pa()&&(m(n),Qr(()=>(t===0&&(i=Ie(()=>e(()=>En(n)))),t+=1,()=>{Dn(()=>{t-=1,t===0&&(i==null||i(),i=void 0,En(n))})})))}}var Yh=li|Ui;function Uh(e,t,n,i){new Xh(e,t,n,i)}var ge,ba,pe,$n,te,ve,Zt,ue,en,ti,_n,Oi,Ls,Rs,nn,qr,Dt,Kh,Gh,Zh,Xo,_r,br,Ko,Go;class Xh{constructor(t,n,i,s){J(this,Dt);z(this,"parent");z(this,"is_pending",!1);z(this,"transform_error");J(this,ge);J(this,ba,null);J(this,pe);J(this,$n);J(this,te);J(this,ve,null);J(this,Zt,null);J(this,ue,null);J(this,en,null);J(this,ti,0);J(this,_n,0);J(this,Oi,!1);J(this,Ls,new Set);J(this,Rs,new Set);J(this,nn,null);J(this,qr,qh(()=>(nt(this,nn,Rn(M(this,ti))),()=>{nt(this,nn,null)})));var r;nt(this,ge,t),nt(this,pe,n),nt(this,$n,o=>{var a=at;a.b=this,a.f|=No,i(o)}),this.parent=at.b,this.transform_error=s??((r=this.parent)==null?void 0:r.transform_error)??(o=>o),nt(this,te,Bs(()=>{lt(this,Dt,Xo).call(this)},Yh))}defer_effect(t){su(t,M(this,Ls),M(this,Rs))}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!M(this,pe).pending}update_pending_count(t,n){lt(this,Dt,Ko).call(this,t,n),nt(this,ti,M(this,ti)+t),!(!M(this,nn)||M(this,Oi))&&(nt(this,Oi,!0),Dn(()=>{nt(this,Oi,!1),M(this,nn)&&Fi(M(this,nn),M(this,ti))}))}get_effect_pending(){return M(this,qr).call(this),m(M(this,nn))}error(t){if(!M(this,pe).onerror&&!M(this,pe).failed)throw t;Z!=null&&Z.is_fork?(M(this,ve)&&Z.skip_effect(M(this,ve)),M(this,Zt)&&Z.skip_effect(M(this,Zt)),M(this,ue)&&Z.skip_effect(M(this,ue)),Z.on_fork_commit(()=>{lt(this,Dt,Go).call(this,t)})):lt(this,Dt,Go).call(this,t)}}ge=new WeakMap,ba=new WeakMap,pe=new WeakMap,$n=new WeakMap,te=new WeakMap,ve=new WeakMap,Zt=new WeakMap,ue=new WeakMap,en=new WeakMap,ti=new WeakMap,_n=new WeakMap,Oi=new WeakMap,Ls=new WeakMap,Rs=new WeakMap,nn=new WeakMap,qr=new WeakMap,Dt=new WeakSet,Kh=function(){try{nt(this,ve,me(()=>M(this,$n).call(this,M(this,ge))))}catch(t){this.error(t)}},Gh=function(t){const n=M(this,pe).failed;n&&nt(this,ue,me(()=>{n(M(this,ge),()=>t,()=>()=>{})}))},Zh=function(){const t=M(this,pe).pending;t&&(this.is_pending=!0,nt(this,Zt,me(()=>t(M(this,ge)))),Dn(()=>{var n=nt(this,en,document.createDocumentFragment()),i=cn();n.append(i),nt(this,ve,lt(this,Dt,br).call(this,()=>me(()=>M(this,$n).call(this,i)))),M(this,_n)===0&&(M(this,ge).before(n),nt(this,en,null),si(M(this,Zt),()=>{nt(this,Zt,null)}),lt(this,Dt,_r).call(this,Z))}))},Xo=function(){try{if(this.is_pending=this.has_pending_snippet(),nt(this,_n,0),nt(this,ti,0),nt(this,ve,me(()=>{M(this,$n).call(this,M(this,ge))})),M(this,_n)>0){var t=nt(this,en,document.createDocumentFragment());Da(M(this,ve),t);const n=M(this,pe).pending;nt(this,Zt,me(()=>n(M(this,ge))))}else lt(this,Dt,_r).call(this,Z)}catch(n){this.error(n)}},_r=function(t){this.is_pending=!1,t.transfer_effects(M(this,Ls),M(this,Rs))},br=function(t){var n=at,i=rt,s=Pt;Ye(M(this,te)),ye(M(this,te)),Ii(M(this,te).ctx);try{return ui.ensure(),t()}catch(r){return nu(r),null}finally{Ye(n),ye(i),Ii(s)}},Ko=function(t,n){var i;if(!this.has_pending_snippet()){this.parent&<(i=this.parent,Dt,Ko).call(i,t,n);return}nt(this,_n,M(this,_n)+t),M(this,_n)===0&&(lt(this,Dt,_r).call(this,n),M(this,Zt)&&si(M(this,Zt),()=>{nt(this,Zt,null)}),M(this,en)&&(M(this,ge).before(M(this,en)),nt(this,en,null)))},Go=function(t){M(this,ve)&&(ie(M(this,ve)),nt(this,ve,null)),M(this,Zt)&&(ie(M(this,Zt)),nt(this,Zt,null)),M(this,ue)&&(ie(M(this,ue)),nt(this,ue,null));var n=M(this,pe).onerror;let i=M(this,pe).failed;var s=!1,r=!1;const o=()=>{if(s){Rh();return}s=!0,r&&kh(),M(this,ue)!==null&&si(M(this,ue),()=>{nt(this,ue,null)}),lt(this,Dt,br).call(this,()=>{lt(this,Dt,Xo).call(this)})},a=l=>{try{r=!0,n==null||n(l,o),r=!1}catch(c){xn(c,M(this,te)&&M(this,te).parent)}i&&nt(this,ue,lt(this,Dt,br).call(this,()=>{try{return me(()=>{var c=at;c.b=this,c.f|=No,i(M(this,ge),()=>l,()=>o)})}catch(c){return xn(c,M(this,te).parent),null}}))};Dn(()=>{var l;try{l=this.transform_error(t)}catch(c){xn(c,M(this,te)&&M(this,te).parent);return}l!==null&&typeof l=="object"&&typeof l.then=="function"?l.then(a,c=>xn(c,M(this,te)&&M(this,te).parent)):a(l)})};function Jh(e,t,n,i){const s=Xi()?Zr:du;var r=e.filter(h=>!h.settled);if(n.length===0&&r.length===0){i(t.map(s));return}var o=at,a=Qh(),l=r.length===1?r[0].promise:r.length>1?Promise.all(r.map(h=>h.promise)):null;function c(h){if((o.f&Ee)===0){a();try{i(h)}catch(f){xn(f,o)}Er()}}var u=uu();if(n.length===0){l.then(()=>c(t.map(s))).finally(u);return}function d(){Promise.all(n.map(h=>$h(h))).then(h=>c([...t.map(s),...h])).catch(h=>xn(h,o)).finally(u)}l?l.then(()=>{a(),d(),Er()}):d()}function Qh(){var e=at,t=rt,n=Pt,i=Z;return function(r=!0){Ye(e),ye(t),Ii(n),r&&(e.f&Ee)===0&&(i==null||i.activate(),i==null||i.apply())}}function Er(e=!0){Ye(null),ye(null),Ii(null),e&&(Z==null||Z.deactivate())}function uu(){var e=at,t=e.b,n=Z,i=t.is_rendered();return t.update_pending_count(1,n),n.increment(i,e),()=>{t.update_pending_count(-1,n),n.decrement(i,e)}}function Zr(e){var t=qt|Vt;return at!==null&&(at.f|=Ui),{ctx:Pt,deps:null,effects:null,equals:$c,f:t,fn:e,reactions:null,rv:0,v:jt,wv:0,parent:at,ac:null}}const Ks=Symbol("obsolete");function $h(e,t,n){let i=at;i===null&&gh();var s=void 0,r=Rn(jt),o=!rt,a=new Set;return ff(()=>{var f;var l=at,c=Uc();s=c.promise;try{Promise.resolve(e()).then(c.resolve,g=>{g!==Gr&&c.reject(g)}).finally(Er)}catch(g){c.reject(g),Er()}var u=Z;if(o){if((l.f&gi)!==0)var d=uu();if(i.b.is_rendered())(f=u.async_deriveds.get(l))==null||f.reject(Ks);else for(const g of a.values())g.reject(Ks);a.add(c),u.async_deriveds.set(l,c)}const h=(g,v=void 0)=>{d==null||d(),a.delete(c),v!==Ks&&(u.activate(),v?(r.f|=An,Fi(r,v)):((r.f&An)!==0&&(r.f^=An),Fi(r,g)),u.deactivate())};c.promise.then(h,g=>h(null,g||"unknown"))}),Ca(()=>{for(const l of a)l.reject(Ks)}),new Promise(l=>{function c(u){function d(){u===s?l(r):c(s)}u.then(d,d)}c(s)})}function Y(e){const t=Zr(e);return Tu(t),t}function du(e){const t=Zr(e);return t.equals=tu,t}function tf(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n0&&!gu&&sf()}return t}function sf(){gu=!1;for(const e of Or){(e.f&Nt)!==0&&Ct(e,qe);let t;try{t=Vs(e)}catch{t=!0}t&&zi(e)}Or.clear()}function En(e){O(e,e.v+1)}function pu(e,t,n){var i=e.reactions;if(i!==null)for(var s=Xi(),r=i.length,o=0;o{if(un===r)return a();var l=rt,c=un;ye(null),rl(r);var u=a();return ye(l),rl(c),u};return i&&n.set("length",j(e.length)),new Proxy(e,{defineProperty(a,l,c){(!("value"in c)||c.configurable===!1||c.enumerable===!1||c.writable===!1)&&xh();var u=n.get(l);return u===void 0?o(()=>{var d=j(c.value);return n.set(l,d),d}):O(u,c.value,!0),!0},deleteProperty(a,l){var c=n.get(l);if(c===void 0){if(l in a){const u=o(()=>j(jt));n.set(l,u),En(s)}}else O(c,jt),En(s);return!0},get(a,l,c){var f;if(l===Tn)return e;var u=n.get(l),d=l in a;if(u===void 0&&(!d||(f=us(a,l))!=null&&f.writable)&&(u=o(()=>{var g=mt(d?a[l]:jt),v=j(g);return v}),n.set(l,u)),u!==void 0){var h=m(u);return h===jt?void 0:h}return Reflect.get(a,l,c)},getOwnPropertyDescriptor(a,l){var c=Reflect.getOwnPropertyDescriptor(a,l);if(c&&"value"in c){var u=n.get(l);u&&(c.value=m(u))}else if(c===void 0){var d=n.get(l),h=d==null?void 0:d.v;if(d!==void 0&&h!==jt)return{enumerable:!0,configurable:!0,value:h,writable:!0}}return c},has(a,l){var h;if(l===Tn)return!0;var c=n.get(l),u=c!==void 0&&c.v!==jt||Reflect.has(a,l);if(c!==void 0||at!==null&&(!u||(h=us(a,l))!=null&&h.writable)){c===void 0&&(c=o(()=>{var f=u?mt(a[l]):jt,g=j(f);return g}),n.set(l,c));var d=m(c);if(d===jt)return!1}return u},set(a,l,c,u){var x;var d=n.get(l),h=l in a;if(i&&l==="length")for(var f=c;fj(jt)),n.set(f+"",g))}if(d===void 0)(!h||(x=us(a,l))!=null&&x.writable)&&(d=o(()=>j(void 0)),O(d,mt(c)),n.set(l,d));else{h=d.v!==jt;var v=o(()=>mt(c));O(d,v)}var p=Reflect.getOwnPropertyDescriptor(a,l);if(p!=null&&p.set&&p.set.call(u,c),!h){if(i&&typeof l=="string"){var _=n.get("length"),b=Number(l);Number.isInteger(b)&&b>=_.v&&O(_,b+1)}En(s)}return!0},ownKeys(a){m(s);var l=Reflect.ownKeys(a).filter(d=>{var h=n.get(d);return h===void 0||h.v!==jt});for(var[c,u]of n)u.v!==jt&&!(c in a)&&l.push(c);return l},setPrototypeOf(){yh()}})}function nl(e){try{if(e!==null&&typeof e=="object"&&Tn in e)return e[Tn]}catch{}return e}function rf(e,t){return Object.is(nl(e),nl(t))}var Zo,vu,mu,_u;function of(){if(Zo===void 0){Zo=window,vu=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;mu=us(t,"firstChild").get,_u=us(t,"nextSibling").get,$a(e)&&(e[Vo]=void 0,e[gr]=null,e[Wo]=void 0,e.__e=void 0),$a(n)&&(n[is]=void 0)}}function cn(e=""){return document.createTextNode(e)}function yn(e){return mu.call(e)}function Ns(e){return _u.call(e)}function w(e,t){return yn(e)}function st(e,t=!1){{var n=yn(e);return n instanceof Comment&&n.data===""?Ns(n):n}}function k(e,t=1,n=!1){let i=e;for(;t--;)i=Ns(i);return i}function af(e){e.textContent=""}function bu(){return!1}function xu(e,t,n){return document.createElementNS(t??Qc,e,void 0)}let il=!1;function lf(){il||(il=!0,document.addEventListener("reset",e=>{Promise.resolve().then(()=>{var t;if(!e.defaultPrevented)for(const n of e.target.elements)(t=n[pr])==null||t.call(n)})},{capture:!0}))}function Jr(e){var t=rt,n=at;ye(null),Ye(null);try{return e()}finally{ye(t),Ye(n)}}function yu(e,t,n,i=n){e.addEventListener(t,()=>Jr(n));const s=e[pr];s?e[pr]=()=>{s(),i(!0)}:e[pr]=()=>i(!0),lf()}function wu(e){at===null&&(rt===null&&_h(),mh()),In&&vh()}function cf(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function Xe(e,t){var n=at;n!==null&&(n.f&Jt)!==0&&(e|=Jt);var i={ctx:Pt,deps:null,nodes:null,f:e|Vt|_e,first:null,fn:t,last:null,next:null,parent:n,b:n&&n.b,prev:null,teardown:null,wv:0,ac:null};Z==null||Z.register_created_effect(i);var s=i;if((e&Ri)!==0)wi!==null?wi.push(i):ui.ensure().schedule(i);else if(t!==null){try{zi(i)}catch(o){throw ie(i),o}s.deps===null&&s.teardown===null&&s.nodes===null&&s.first===s.last&&(s.f&Ui)===0&&(s=s.first,(e&Ae)!==0&&(e&li)!==0&&s!==null&&(s.f|=li))}if(s!==null&&(s.parent=n,n!==null&&cf(s,n),rt!==null&&(rt.f&qt)!==0&&(e&Ln)===0)){var r=rt;(r.effects??(r.effects=[])).push(s)}return i}function Pa(){return rt!==null&&!Te}function Ca(e){const t=Xe(zs,null);return Ct(t,Nt),t.teardown=e,t}function be(e){wu();var t=at.f,n=!rt&&(t&Re)!==0&&(t&gi)===0;if(n){var i=Pt;(i.e??(i.e=[])).push(e)}else return ku(e)}function ku(e){return Xe(Ri|Gc,e)}function uf(e){return wu(),Xe(zs|Gc,e)}function df(e){ui.ensure();const t=Xe(Ln|Ui,e);return(n={})=>new Promise(i=>{n.outro?si(t,()=>{ie(t),i(void 0)}):(ie(t),i(void 0))})}function hf(e){return Xe(Ri,e)}function ff(e){return Xe(Mi|Ui,e)}function Qr(e,t=0){return Xe(zs|t,e)}function W(e,t=[],n=[],i=[]){Jh(i,t,n,s=>{Xe(zs,()=>e(...s.map(m)))})}function Bs(e,t=0){var n=Xe(Ae|t,e);return n}function me(e){return Xe(Re|Ui,e)}function Su(e){var t=e.teardown;if(t!==null){const n=In,i=rt;sl(!0),ye(null);try{t.call(null)}finally{sl(n),ye(i)}}}function Aa(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){const s=n.ac;s!==null&&Jr(()=>{s.abort(Gr)});var i=n.next;(n.f&Ln)!==0?n.parent=null:ie(n,t),n=i}}function gf(e){for(var t=e.first;t!==null;){var n=t.next;(t.f&Re)===0&&ie(t),t=n}}function ie(e,t=!0){var n=!1;(t||(e.f&dh)!==0)&&e.nodes!==null&&e.nodes.end!==null&&(Mu(e.nodes.start,e.nodes.end),n=!0),Ct(e,Bo),Aa(e,t&&!n),ks(e,0);var i=e.nodes&&e.nodes.t;if(i!==null)for(const r of i)r.stop();Su(e),e.f^=Bo,e.f|=Ee;var s=e.parent;s!==null&&s.first!==null&&Pu(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=e.b=null}function Mu(e,t){for(;e!==null;){var n=e===t?null:Ns(e);e.remove(),e=n}}function Pu(e){var t=e.parent,n=e.prev,i=e.next;n!==null&&(n.next=i),i!==null&&(i.prev=n),t!==null&&(t.first===e&&(t.first=i),t.last===e&&(t.last=n))}function si(e,t,n=!0){var i=[];Cu(e,i,!0);var s=()=>{n&&ie(e),t&&t()},r=i.length;if(r>0){var o=()=>--r||s();for(var a of i)a.out(o)}else s()}function Cu(e,t,n){if((e.f&Jt)===0){e.f^=Jt;var i=e.nodes&&e.nodes.t;if(i!==null)for(const a of i)(a.is_global||n)&&t.push(a);for(var s=e.first;s!==null;){var r=s.next;if((s.f&Ln)===0){var o=(s.f&li)!==0||(s.f&Re)!==0&&(e.f&Ae)!==0;Cu(s,t,o?n:!1)}s=r}}}function Ta(e){Au(e,!0)}function Au(e,t){if((e.f&Jt)!==0){e.f^=Jt,(e.f&Nt)===0&&(Ct(e,Vt),ui.ensure().schedule(e));for(var n=e.first;n!==null;){var i=n.next,s=(n.f&li)!==0||(n.f&Re)!==0;Au(n,s?t:!1),n=i}var r=e.nodes&&e.nodes.t;if(r!==null)for(const o of r)(o.is_global||t)&&o.in()}}function Da(e,t){if(e.nodes)for(var n=e.nodes.start,i=e.nodes.end;n!==null;){var s=n===i?null:Ns(n);t.append(n),n=s}}let xr=!1,In=!1;function sl(e){In=e}let rt=null,Te=!1;function ye(e){rt=e}let at=null;function Ye(e){at=e}let xe=null;function Tu(e){rt!==null&&(xe===null?xe=[e]:xe.push(e))}let ee=null,ae=0,fe=null;function pf(e){fe=e}let Du=1,Xn=0,un=Xn;function rl(e){un=e}function Eu(){return++Du}function Vs(e){var t=e.f;if((t&Vt)!==0)return!0;if(t&qt&&(e.f&=~ci),(t&qe)!==0){for(var n=e.deps,i=n.length,s=0;se.wv)return!0}(t&_e)!==0&&Ht===null&&Ct(e,Nt)}return!1}function Ou(e,t,n=!0){var i=e.reactions;if(i!==null&&!(xe!==null&&ni.call(xe,e)))for(var s=0;s{e.ac.abort(Gr)}),e.ac=null);try{e.f|=Dr;var u=e.fn,d=u();e.f|=gi;var h=e.deps,f=Z==null?void 0:Z.is_fork;if(ee!==null){var g;if(f||ks(e,ae),h!==null&&ae>0)for(h.length=ae+ee.length,g=0;gn==null?void 0:n.call(this,r))}return e.startsWith("pointer")||e.startsWith("touch")||e==="wheel"?Dn(()=>{t.addEventListener(e,s,i)}):t.addEventListener(e,s,i),s}function Ea(e,t,n,i,s){var r={capture:i,passive:s},o=yf(e,t,n,r);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&Ca(()=>{t.removeEventListener(e,o,r)})}function K(e,t,n){(t[Kn]??(t[Kn]={}))[e]=n}function Et(e){for(var t=0;t{throw b});throw h}}finally{e[Kn]=t,delete e.currentTarget,ye(u),Ye(d)}}}var Wc;const po=((Wc=globalThis==null?void 0:globalThis.window)==null?void 0:Wc.trustedTypes)&&globalThis.window.trustedTypes.createPolicy("svelte-trusted-html",{createHTML:e=>e});function wf(e){return(po==null?void 0:po.createHTML(e))??e}function kf(e){var t=xu("template");return t.innerHTML=wf(e.replaceAll("","")),t.content}function ji(e,t){var n=at;n.nodes===null&&(n.nodes={start:e,end:t,a:null,t:null})}function L(e,t){var n=(t&Ah)!==0,i=(t&Th)!==0,s,r=!e.startsWith("");return()=>{s===void 0&&(s=kf(r?e:""+e),n||(s=yn(s)));var o=i||vu?document.importNode(s,!0):s.cloneNode(!0);if(n){var a=yn(o),l=o.lastChild;ji(a,l)}else ji(o,o);return o}}function Ft(e=""){{var t=cn(e+"");return ji(t,t),t}}function Bt(){var e=document.createDocumentFragment(),t=document.createComment(""),n=cn();return e.append(t,n),ji(t,n),e}function P(e,t){e!==null&&e.before(t)}function F(e,t){var n=t==null?"":typeof t=="object"?`${t}`:t;n!==(e[is]??(e[is]=e.nodeValue))&&(e[is]=n,e.nodeValue=`${n}`)}function Sf(e,t){return Mf(e,t)}const Gs=new Map;function Mf(e,{target:t,anchor:n,props:i={},events:s,context:r,intro:o=!0,transformError:a}){of();var l=void 0,c=df(()=>{var u=n??t.appendChild(cn());Uh(u,{pending:()=>{}},f=>{tt({});var g=Pt;r&&(g.c=r),s&&(i.$$events=s),l=e(f,i)||{},et()},a);var d=new Set,h=f=>{for(var g=0;g{var p;for(var f of d)for(const _ of[t,document]){var g=Gs.get(_),v=g.get(f);--v==0?(_.removeEventListener(f,$o),g.delete(f),g.size===0&&Gs.delete(_)):g.set(f,v)}Qo.delete(h),u!==n&&((p=u.parentNode)==null||p.removeChild(u))}});return Pf.set(l,c),l}let Pf=new WeakMap;var Pe,Be,de,ei,Is,Fs,Yr;class Oa{constructor(t,n=!0){z(this,"anchor");J(this,Pe,new Map);J(this,Be,new Map);J(this,de,new Map);J(this,ei,new Set);J(this,Is,!0);J(this,Fs,t=>{if(M(this,Pe).has(t)){var n=M(this,Pe).get(t),i=M(this,Be).get(n);if(i)Ta(i),M(this,ei).delete(n);else{var s=M(this,de).get(n);s&&(M(this,Be).set(n,s.effect),M(this,de).delete(n),s.fragment.lastChild.remove(),this.anchor.before(s.fragment),i=s.effect)}for(const[r,o]of M(this,Pe)){if(M(this,Pe).delete(r),r===t)break;const a=M(this,de).get(o);a&&(ie(a.effect),M(this,de).delete(o))}for(const[r,o]of M(this,Be)){if(r===n||M(this,ei).has(r))continue;const a=()=>{if(Array.from(M(this,Pe).values()).includes(r)){var c=document.createDocumentFragment();Da(o,c),c.append(cn()),M(this,de).set(r,{effect:o,fragment:c})}else ie(o);M(this,ei).delete(r),M(this,Be).delete(r)};M(this,Is)||!i?(M(this,ei).add(r),si(o,a,!1)):a()}}});J(this,Yr,t=>{M(this,Pe).delete(t);const n=Array.from(M(this,Pe).values());for(const[i,s]of M(this,de))n.includes(i)||(ie(s.effect),M(this,de).delete(i))});this.anchor=t,nt(this,Is,n)}ensure(t,n){var i=Z,s=bu();if(n&&!M(this,Be).has(t)&&!M(this,de).has(t))if(s){var r=document.createDocumentFragment(),o=cn();r.append(o),M(this,de).set(t,{effect:me(()=>n(o)),fragment:r})}else M(this,Be).set(t,me(()=>n(this.anchor)));if(M(this,Pe).set(i,t),s){for(const[a,l]of M(this,Be))a===t?i.unskip_effect(l):i.skip_effect(l);for(const[a,l]of M(this,de))a===t?i.unskip_effect(l.effect):i.skip_effect(l.effect);i.oncommit(M(this,Fs)),i.ondiscard(M(this,Yr))}else M(this,Fs).call(this,i)}}Pe=new WeakMap,Be=new WeakMap,de=new WeakMap,ei=new WeakMap,Is=new WeakMap,Fs=new WeakMap,Yr=new WeakMap;function H(e,t,n=!1){var i=new Oa(e),s=n?li:0;function r(o,a){i.ensure(o,a)}Bs(()=>{var o=!1;t((a,l=0)=>{o=!0,r(l,a)}),o||r(-1,null)},s)}const Cf=Symbol("NaN");function Af(e,t,n){var i=new Oa(e),s=!Xi();Bs(()=>{var r=t();r!==r&&(r=Cf),s&&r!==null&&typeof r=="object"&&(r={}),i.ensure(r,n)})}function zu(e,t){return t}function Tf(e,t,n){for(var i=[],s=t.length,r,o=t.length,a=0;a{if(r){if(r.pending.delete(d),r.done.add(d),r.pending.size===0){var h=e.outrogroups;ta(e,Kr(r.done)),h.delete(r),h.size===0&&(e.outrogroups=null)}}else o-=1},!1)}if(o===0){var l=i.length===0&&n!==null;if(l){var c=n,u=c.parentNode;af(u),u.append(c),e.items.clear()}ta(e,t,!l)}else r={pending:new Set(t),done:new Set},(e.outrogroups??(e.outrogroups=new Set)).add(r)}function ta(e,t,n=!0){var i;if(e.pending.size>0){i=new Set;for(const o of e.pending.values())for(const a of o)i.add(e.items.get(a).e)}for(var s=0;s{var x=n();return xa(x)?x:x==null?[]:Kr(x)}),h,f=new Map,g=!0;function v(x){(b.effect.f&Ee)===0&&(b.pending.delete(x),b.fallback=u,Df(b,h,o,t,i),u!==null&&(h.length===0?(u.f&We)===0?Ta(u):(u.f^=We,rs(u,null,o)):si(u,()=>{u=null})))}function p(x){b.pending.delete(x)}var _=Bs(()=>{h=m(d);for(var x=h.length,y=new Set,S=Z,D=bu(),T=0;Tr(o)):(u=me(()=>r(al??(al=cn()))),u.f|=We)),x>y.size&&ph(),!g)if(f.set(S,y),D){for(const[I,C]of a)y.has(I)||S.skip_effect(C.e);S.oncommit(v),S.ondiscard(p)}else v(S);m(d)}),b={effect:_,items:a,pending:f,outrogroups:null,fallback:u};g=!1}function Gi(e){for(;e!==null&&(e.f&Re)===0;)e=e.next;return e}function Df(e,t,n,i,s){var R,I,C,V,q,N,X,G,B;var r=(i&Ph)!==0,o=t.length,a=e.items,l=Gi(e.effect.first),c,u=null,d,h=[],f=[],g,v,p,_;if(r)for(_=0;_0){var E=(i&Jc)!==0&&o===0?n:null;if(r){for(_=0;_{var U,ct;if(d!==void 0)for(p of d)(ct=(U=p.nodes)==null?void 0:U.a)==null||ct.apply()})}function Ef(e,t,n,i,s,r,o,a){var l=(o&Sh)!==0?(o&Ch)===0?nf(n,!1,!1):Rn(n):null,c=(o&Mh)!==0?Rn(s):null;return{v:l,i:c,e:me(()=>(r(t,l??n,c??s,a),()=>{e.delete(i)}))}}function rs(e,t,n){if(e.nodes)for(var i=e.nodes.start,s=e.nodes.end,r=t&&(t.f&We)===0?t.nodes.start:n;i!==null;){var o=Ns(i);if(r.before(i),i===s)return;i=o}}function gn(e,t,n){t===null?e.effect.first=n:t.next=n,n===null?e.effect.last=t:n.prev=t}function ju(e,t,n=!1,i=!1,s=!1,r=!1){var o=e,a="";if(n)var l=e;W(()=>{var c=at;if(a!==(a=t()??"")){if(n){c.nodes=null,l.innerHTML=a,a!==""&&ji(yn(l),l.lastChild);return}if(c.nodes!==null&&(Mu(c.nodes.start,c.nodes.end),c.nodes=null),a!==""){var u=i?Dh:s?Eh:void 0,d=xu(i?"svg":s?"math":"template",u);d.innerHTML=a;var h=i||s?d:d.content;if(ji(yn(h),h.lastChild),i||s)for(;yn(h);)o.before(yn(h));else o.before(h)}}})}function Ws(e,t,...n){var i=new Oa(e);Bs(()=>{const s=t()??null;i.ensure(s,s&&(r=>s(r,...n)))},li)}function Nu(e){var t,n,i="";if(typeof e=="string"||typeof e=="number")i+=e;else if(typeof e=="object")if(Array.isArray(e)){var s=e.length;for(t=0;t=0;){var a=o+r;(o===0||ll.includes(i[o-1]))&&(a===i.length||ll.includes(i[a]))?i=(o===0?"":i.substring(0,o))+i.substring(a+1):o=a}}return i===""?null:i}function cl(e,t=!1){var n=t?" !important;":";",i="";for(var s of Object.keys(e)){var r=e[s];r!=null&&r!==""&&(i+=" "+s+": "+r+n)}return i}function vo(e){return e[0]!=="-"||e[1]!=="-"?e.toLowerCase():e}function If(e,t){if(t){var n="",i,s;if(Array.isArray(t)?(i=t[0],s=t[1]):i=t,e){e=String(e).replaceAll(/\s*\/\*.*?\*\/\s*/g,"").trim();var r=!1,o=0,a=!1,l=[];i&&l.push(...Object.keys(i).map(vo)),s&&l.push(...Object.keys(s).map(vo));var c=0,u=-1;const v=e.length;for(var d=0;d{to(e,e.__value)});t.observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["value"]}),Ca(()=>{t.disconnect()})}function ul(e){return"__value"in e?e.__value:e.value}const Ff=Symbol("is custom element"),zf=Symbol("is html"),jf=fh?"progress":"PROGRESS";function Nf(e,t){var n=Bu(e);n.value===(n.value=t??void 0)||e.value===t&&(t!==0||e.nodeName!==jf)||(e.value=t??"")}function _t(e,t,n,i){var s=Bu(e);s[t]!==(s[t]=n)&&(t==="loading"&&(e[hh]=n),n==null?e.removeAttribute(t):typeof n!="string"&&Bf(e).includes(t)?e[t]=n:e.setAttribute(t,n))}function Bu(e){return e[gr]??(e[gr]={[Ff]:e.nodeName.includes("-"),[zf]:e.namespaceURI===Qc})}var dl=new Map;function Bf(e){var t=e.getAttribute("is")||e.nodeName,n=dl.get(t);if(n)return n;dl.set(t,n=[]);for(var i,s=e,r=Element.prototype;r!==s;){i=Yc(s);for(var o in i)i[o].set&&o!=="innerHTML"&&o!=="textContent"&&o!=="innerText"&&n.push(o);s=ya(s)}return n}function Ni(e,t,n=t){var i=new WeakSet;yu(e,"input",async s=>{var r=s?e.defaultValue:e.value;if(r=_o(e)?bo(r):r,n(r),Z!==null&&i.add(Z),await mf(),r!==(r=t())){var o=e.selectionStart,a=e.selectionEnd,l=e.value.length;if(e.value=r??"",a!==null){var c=e.value.length;o===a&&a===l&&c>l?(e.selectionStart=c,e.selectionEnd=c):(e.selectionStart=o,e.selectionEnd=Math.min(a,c))}}}),Ie(t)==null&&e.value&&(n(_o(e)?bo(e.value):e.value),Z!==null&&i.add(Z)),Qr(()=>{var s=t();if(e===document.activeElement){var r=Z;if(i.has(r))return}_o(e)&&s===bo(e.value)||e.type==="date"&&!s&&!e.value||s!==e.value&&(e.value=s??"")})}function Vf(e,t,n=t){yu(e,"change",i=>{var s=i?e.defaultChecked:e.checked;n(s)}),Ie(t)==null&&n(e.checked),Qr(()=>{var i=t();e.checked=!!i})}function _o(e){var t=e.type;return t==="number"||t==="range"}function bo(e){return e===""?null:+e}function xo(e,t){return e===t||(e==null?void 0:e[Tn])===t}function Bi(e={},t,n,i){var s=Pt.r,r=at;return hf(()=>{var o,a;return Qr(()=>{o=a,a=[],Ie(()=>{xo(n(...a),e)||(t(e,...a),o&&xo(n(...o),e)&&t(null,...o))})}),()=>{let l=r;for(;l!==s&&l.parent!==null&&l.parent.f&Bo;)l=l.parent;const c=()=>{a&&xo(n(...a),e)&&t(null,...a)},u=l.teardown;l.teardown=()=>{c(),u==null||u()}}}),e}function Ra(e=!1){const t=Pt,n=t.l.u;if(!n)return;let i=()=>_f(t.s);if(e){let s=0,r={};const o=Zr(()=>{let a=!1;const l=t.s;for(const c in l)l[c]!==r[c]&&(r[c]=l[c],a=!0);return a&&s++,s});i=()=>m(o)}n.b.length&&uf(()=>{hl(t,i),jo(n.b)}),be(()=>{const s=Ie(()=>n.m.map(uh));return()=>{for(const r of s)typeof r=="function"&&r()}}),n.a.length&&be(()=>{hl(t,i),jo(n.a)})}function hl(e,t){if(e.l.s)for(const n of e.l.s)m(n);t()}function Gt(e,t,n,i){var s=i,r=!0,o=()=>(r&&(r=!1,s=i),s),a;a=e[t],a===void 0&&i!==void 0&&(a=o());var l;return l=()=>{var c=e[t];return c===void 0?o():(r=!0,c)},l}function dn(e){Pt===null&&Zc(),js&&Pt.l!==null?Wf(Pt).m.push(e):be(()=>{const t=Ie(e);if(typeof t=="function")return t})}function Vu(e){Pt===null&&Zc(),dn(()=>()=>Ie(e))}function Wf(e){var t=e.l;return t.u??(t.u={a:[],b:[],m:[]})}const Hf="5";var Hc;typeof window<"u"&&((Hc=window.__svelte??(window.__svelte={})).v??(Hc.v=new Set)).add(Hf);const fl="serena-dashboard-theme";function qf(){let e=j("light");function t(n){O(e,n,!0),document.documentElement.setAttribute("data-theme",n)}return{get current(){return m(e)},init(){var s;const n=localStorage.getItem(fl),i=(s=window.matchMedia)==null?void 0:s.call(window,"(prefers-color-scheme: dark)").matches;t(n??(i?"dark":"light"))},toggle(){const n=m(e)==="dark"?"light":"dark";t(n),localStorage.setItem(fl,n)}}}const ri=qf();var Yf=L('');function Uf(e,t){tt(t,!0);const n=Y(()=>ri.current==="dark"?"Switch to light theme":"Switch to dark theme");var i=Yf(),s=w(i),r=w(s);W(()=>{_t(i,"aria-label",m(n)),_t(i,"title",m(n)),F(r,ri.current==="dark"?"☀":"🌙")}),K("click",i,()=>ri.toggle()),P(e,i),et()}Et(["click"]);const Xf="https://oraios-software.de/serena-banners/manifest.php";function Kf(e,t){return e[t]??[]}function Gf(e,t){return t==="dark"?e.image_dark??e.image:e.image}async function Zf(){try{const e=await fetch(Xf);return e.ok?await e.json():{}}catch{return{}}}var Jf=L('
                    ');function Wu(e,t){tt(t,!0);let n=j(mt([])),i=j(0),s=null;async function r(){O(n,Kf(await Zf(),t.target),!0),m(n).length&&O(i,Math.floor(Math.random()*m(n).length),!0),m(n).length>1&&(s=setInterval(()=>{O(i,(m(i)+1)%m(n).length)},6e3))}dn(()=>{r()}),Vu(()=>{s&&clearInterval(s)});const o=Y(()=>m(n)[m(i)]);var a=Bt(),l=st(a);{var c=u=>{var d=Jf(),h=w(d),f=w(h);W(g=>{At(d,1,`banner ${t.target??""}`,"svelte-1lo7lbo"),_t(h,"href",m(o).link),_t(f,"src",g),_t(f,"alt",m(o).alt??"")},[()=>Gf(m(o),ri.current)]),P(u,d)};H(l,u=>{m(o)&&u(c)})}P(e,a),et()}var Qf=L('
                    ');function $f(e,t){tt(t,!0);const n=Y(()=>ri.current==="dark"?"serena-logo-dark-mode.svg":"serena-logo.svg");var i=Qf(),s=w(i),r=w(s),o=w(r),a=k(r,2),l=w(a);Wu(l,{target:"platinum"});var c=k(s,2),u=w(c),d=w(u);Uf(d,{});var h=k(d,2),f=k(u,2),g=w(f);let v;var p=k(g,2);let _;var b=k(p,2);let x;var y=k(b,2);let S;W(()=>{_t(o,"src",m(n)),v=At(g,1,"header-tab svelte-xjepwq",null,v,{active:t.active==="overview"}),_t(g,"aria-current",t.active==="overview"?"page":void 0),_=At(p,1,"header-tab svelte-xjepwq",null,_,{active:t.active==="logs"}),_t(p,"aria-current",t.active==="logs"?"page":void 0),x=At(b,1,"header-tab svelte-xjepwq",null,x,{active:t.active==="stats"}),_t(b,"aria-current",t.active==="stats"?"page":void 0),S=At(y,1,"header-tab svelte-xjepwq",null,S,{active:t.active==="code"}),_t(y,"aria-current",t.active==="code"?"page":void 0)}),K("click",h,()=>t.onshutdown()),K("click",g,()=>t.onnavigate("overview")),K("click",p,()=>t.onnavigate("logs")),K("click",b,()=>t.onnavigate("stats")),K("click",y,()=>t.onnavigate("code")),P(e,i),et()}Et(["click"]);function Zi(e,t){let n=null,i=!1;const s=async()=>{if(!i){i=!0;try{await e()}finally{i=!1}}};return{start(){n||(s(),n=setInterval(()=>void s(),t))},stop(){n&&(clearInterval(n),n=null),i=!1}}}function tg(e){switch(e){case"overview":return["config","queued","last","timeline"];case"logs":return["logs"];case"stats":return["config","timeline"];case"code":return["config"]}}class eg extends Error{constructor(t,n){super(n),this.status=t}}async function Ia(e){if(!e.ok)throw new eg(e.status,`HTTP ${e.status} for ${e.url}`);return await e.json()}async function re(e){return Ia(await fetch(e))}async function we(e,t){return Ia(await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t??{})}))}async function ng(e){return Ia(await fetch(e,{method:"PUT"}))}const ig=()=>re("/get_config_overview"),sg=e=>we("/get_log_messages",{start_idx:e}),rg=()=>we("/clear_logs"),og=()=>re("/get_tool_names"),ag=()=>re("/get_tool_stats"),lg=()=>we("/clear_tool_stats"),cg=()=>re("/get_token_count_estimator_name"),ug=()=>ng("/shutdown"),dg=()=>re("/get_available_languages"),hg=e=>we("/add_language",{language:e}),fg=e=>we("/remove_language",{language:e}),gg=e=>we("/get_memory",{memory_name:e}),Hu=(e,t)=>we("/save_memory",{memory_name:e,content:t}),pg=e=>we("/delete_memory",{memory_name:e}),vg=(e,t)=>we("/rename_memory",{old_name:e,new_name:t}),mg=()=>re("/get_serena_config"),_g=e=>we("/save_serena_config",{content:e}),bg=()=>re("/queued_task_executions"),xg=e=>we("/cancel_task_execution",{task_id:e}),yg=()=>re("/last_execution"),wg=()=>re("/fetch_unread_news"),kg=e=>we("/mark_news_snippet_as_read",{news_snippet_id:e}),Sg=e=>{const t=new URLSearchParams;e.since_seq!==void 0&&t.set("since_seq",String(e.since_seq)),e.tool&&t.set("tool",e.tool),t.set("limit",String(e.limit));const n=t.toString(),i=n?`/get_tool_call_timeline?${n}`:"/get_tool_call_timeline";return re(i)},Mg=e=>re(`/code/list_dir?path=${encodeURIComponent(e)}`),Pg=e=>re(`/code/file_symbols?path=${encodeURIComponent(e)}`),Cg=(e,t=50)=>re(`/code/workspace_symbol_search?q=${encodeURIComponent(e)}&limit=${t}`),Ag=(e=1e3)=>re(`/code/diagnostics_summary?file_limit=${e}`);function Tg(){let e=j(null),t="";return{get data(){return m(e)},async poll(){const n=await ig(),i=JSON.stringify(n);i!==t&&(O(e,n,!0),t=i)}}}const Lr=Tg();async function Oe(e){try{const t=await e();return t&&t.status==="error"?{ok:!1,message:t.message??"Request failed",data:t}:{ok:!0,data:t}}catch(t){return{ok:!1,message:t instanceof Error?t.message:"Request failed"}}}function Dg(){let e=j(mt([])),t=j(null),n=j(mt([])),i=j("");const s=new Set;return{get queued(){return m(e)},get last(){return m(t)},get cancelled(){return m(n)},get cancelError(){return m(i)},clearCancelError(){O(i,"")},async pollQueued(){O(e,(await bg()).queued_executions,!0)},async pollLast(){const r=(await yg()).last_execution;O(t,r&&r.logged?r:null,!0)},async cancel(r){if(s.has(r.task_id))return{ok:!1};s.add(r.task_id),O(i,"");try{const o=await Oe(()=>xg(r.task_id));return o.ok?(m(n).some(a=>a.task_id===r.task_id)||O(n,[...m(n),r],!0),o):(O(i,o.message??"Failed to cancel execution",!0),o)}finally{s.delete(r.task_id)}}}}const Ve=Dg();function Eg(){let e=j(mt([])),t=j(0),n=j(null);return{get lines(){return m(e)},get activeProject(){return m(n)},async poll(){const i=await sg(m(t));i.messages.length&&O(e,[...m(e),...i.messages],!0),O(t,i.max_idx,!0),O(n,i.active_project,!0)},async clear(){await rg(),O(e,[],!0),O(t,0)}}}const yr=Eg(),gl=2e3,Og=200;function Lg(){let e=j(mt([])),t=j(null),n=j(null),i=j(!1),s=j(1),r=j(25),o=j(null),a=j(null);function l(c){if(c.length===0)return;const u=new Set(m(e).map(g=>g.seq)),d=c.filter(g=>!u.has(g.seq));if(d.length===0)return;d.sort((g,v)=>v.seq-g.seq);const h=m(e).length>0?m(e)[0].seq:-1/0;let f;d[d.length-1].seq>h?f=[...d,...m(e)]:(f=[...d,...m(e)],f.sort((g,v)=>v.seq-g.seq)),f.length>gl&&(f.length=gl),O(e,f,!0)}return{get records(){return m(e)},get cursor(){return m(t)},get filter(){return m(n)},get paused(){return m(i)},get page(){return m(s)},get pageSize(){return m(r)},get pausedGap(){return m(o)},get error(){return m(a)},async poll(){if(!m(i))try{const c=await Sg({since_seq:m(t)??void 0,tool:m(n)??void 0,limit:Og}),u=c.max_seq??m(t);if(m(t)!==null&&u!==null&&u-m(t)>c.records.length){const d=u-m(t)-c.records.length;d>0&&O(o,d)}l(c.records),O(t,u,!0),O(a,null)}catch(c){O(a,c instanceof Error?c.message:String(c),!0)}},pause(){O(i,!0)},resume(){O(i,!1),O(o,null)},togglePause(){O(i,!m(i)),m(i)||O(o,null)},clearView(){O(e,[],!0),O(o,null),O(s,1)},setFilter(c){O(n,c,!0),O(e,[],!0),O(t,null),O(s,1)},setPage(c){O(s,Math.max(1,c),!0)},setPageSize(c){O(r,c,!0),O(s,1)}}}const di=Lg();function Rg(e){return e?`${e} – Serena Dashboard`:"Serena Dashboard"}var Ig=L('
                    '),Fg=L('

                    ');function Rr(e,t){let n=Gt(t,"open",3,!1),i=j(mt(n()));function s(){O(i,!m(i))}var r=Fg(),o=w(r),a=w(o),l=w(a),c=w(l),u=k(l,2);let d;var h=k(o,2);{var f=g=>{var v=Ig(),p=w(v);Ws(p,()=>t.children),P(g,v)};H(h,g=>{m(i)&&g(f)})}W(()=>{_t(a,"aria-expanded",m(i)),F(c,t.title),d=At(u,1,"toggle-icon svelte-oy8gea",null,d,{open:m(i)})}),K("click",a,s),P(e,r)}Et(["click"]);var zg=L("");function se(e,t){let n=Gt(t,"variant",3,"primary"),i=Gt(t,"disabled",3,!1);var s=zg(),r=w(s);Ws(r,()=>t.children),W(()=>{At(s,1,`btn ${n()??""}`,"svelte-18f749u"),s.disabled=i()}),K("click",s,function(...o){var a;(a=t.onclick)==null||a.apply(this,o)}),P(e,s)}Et(["click"]);var jg=L(" "),Ng=L(''),Bg=L(' '),Vg=L(' '),Wg=L(" ",1),Hg=L(' '),qg=L('
                    '),Yg=L('
                    '),Ug=L('
                    '),Xg=L('
                    Version Active Project Languages Context Active Modes File Encoding
                    ');function Kg(e,t){tt(t,!0);var n=Xg(),i=w(n),s=k(w(i),2),r=w(s),o=k(s,4),a=w(o);{var l=C=>{var V=jg(),q=w(V);W(()=>{_t(V,"title",`Project configuration in ${t.data.active_project.path??""}/.serena/project.yml`),F(q,t.data.active_project.name)}),P(C,V)},c=C=>{var V=Ft();W(()=>{var q;return F(V,((q=t.data.active_project)==null?void 0:q.name)??"None")}),P(C,V)};H(a,C=>{var V,q;(V=t.data.active_project)!=null&&V.name&&((q=t.data.active_project)!=null&&q.path)?C(l):C(c,-1)})}var u=k(o,4),d=w(u);{var h=C=>{var V=Ft("Using JetBrains backend");P(C,V)},f=C=>{var V=Vg(),q=w(V);Mt(q,16,()=>t.data.languages,G=>G,(G,B)=>{var U=Bg(),ct=w(U),bt=k(ct);{var dt=vt=>{var Ot=Ng();W(()=>_t(Ot,"aria-label",`Remove ${B??""}`)),K("click",Ot,()=>t.onremovelanguage(B)),P(vt,Ot)};H(bt,vt=>{t.data.languages.length>1&&vt(dt)})}W(()=>F(ct,`${B??""} `)),P(G,U)});var N=k(q,2);{var X=G=>{se(G,{variant:"secondary",get onclick(){return t.onaddlanguage},children:(B,U)=>{var ct=Ft("Add Language");P(B,ct)},$$slots:{default:!0}})};H(N,G=>{var B;(B=t.data.active_project)!=null&&B.name&&G(X)})}P(C,V)};H(d,C=>{t.data.jetbrains_mode?C(h):C(f,-1)})}var g=k(u,4),v=w(g),p=w(v),_=k(g,4),b=w(_);{var x=C=>{var V=Bt(),q=st(V);Mt(q,19,()=>t.data.modes,N=>N.name,(N,X,G)=>{var B=Wg(),U=st(B),ct=w(U),bt=k(U);{var dt=vt=>{var Ot=Ft(",");P(vt,Ot)};H(bt,vt=>{m(G){_t(U,"title",m(X).path),F(ct,m(X).name)}),P(N,B)}),P(C,V)},y=C=>{var V=Ft("None");P(C,V)};H(b,C=>{t.data.modes.length?C(x):C(y,-1)})}var S=k(_,4),D=w(S),T=k(i,2);Rr(T,{get title(){return`Active Tools (${t.data.active_tools.length??""})`},children:(C,V)=>{var q=qg();Mt(q,20,()=>t.data.active_tools,N=>N,(N,X)=>{var G=Hg(),B=w(G);W(()=>F(B,X)),P(N,G)}),P(C,q)},$$slots:{default:!0}});var A=k(T,2);{var E=C=>{Rr(C,{get title(){return`Memories (${t.data.available_memories.length??""})`},children:(V,q)=>{var N=Ug(),X=w(N);Mt(X,16,()=>t.data.available_memories,B=>B,(B,U)=>{var ct=Yg(),bt=w(ct),dt=w(bt),vt=k(bt,2);W(()=>{F(dt,U),_t(vt,"aria-label",`Delete memory ${U??""}`)}),K("click",bt,()=>t.onopenmemory(U)),K("click",vt,()=>t.ondeletememory(U)),P(B,ct)});var G=k(X,2);K("click",G,function(...B){var U;(U=t.oncreatememory)==null||U.apply(this,B)}),P(V,N)},$$slots:{default:!0}})};H(A,C=>{t.data.available_memories&&C(E)})}var R=k(A,2),I=k(w(R),2);se(I,{variant:"secondary",get onclick(){return t.oneditconfig},children:(C,V)=>{var q=Ft("Edit Global Serena Config");P(C,q)},$$slots:{default:!0}}),W(()=>{F(r,t.data.serena_version),_t(v,"title",t.data.context.path),F(p,t.data.context.name),F(D,t.data.encoding??"N/A")}),P(e,n),et()}Et(["click"]);var Gg=L('
                    '),Zg=L('
                    '),Jg=L('
                    No tool usage yet.
                    ');function Qg(e,t){tt(t,!0);const n=Y(()=>Object.entries(t.stats).sort((l,c)=>c[1].num_calls-l[1].num_calls)),i=Y(()=>Math.max(1,...m(n).map(([,l])=>l.num_calls)));var s=Bt(),r=st(s);{var o=l=>{var c=Zg();Mt(c,21,()=>m(n),([u,d])=>u,(u,d)=>{var h=Y(()=>Xc(m(d),2));let f=()=>m(h)[0],g=()=>m(h)[1];var v=Gg(),p=w(v),_=w(p),b=k(p,2),x=w(b),y=k(b,2),S=w(y);W(()=>{F(_,f()),$r(x,`width:${g().num_calls/m(i)*100}%`),F(S,g().num_calls)}),P(u,v)}),P(l,c)},a=l=>{var c=Jg();P(l,c)};H(r,l=>{m(n).length?l(o):l(a,-1)})}P(e,s),et()}var $g=L('

                    '),tp=L('
                    ');function pn(e,t){let n=Gt(t,"title",3,"");var i=tp(),s=w(i);{var r=a=>{var l=$g(),c=w(l);W(()=>F(c,n())),P(a,l)};H(s,a=>{n()&&a(r)})}var o=k(s,2);Ws(o,()=>t.children),P(e,i)}var ep=L("
                  • "),np=L('
                      '),ip=L('
                      None.
                      ');function yo(e,t){tt(t,!0),pn(e,{children:(n,i)=>{Rr(n,{get title(){return t.title},children:(s,r)=>{var o=Bt(),a=st(o);{var l=u=>{var d=np();Mt(d,21,()=>t.items,h=>h.name,(h,f)=>{var g=ep();let v;var p=w(g);W(()=>{v=At(g,1,"svelte-1mlfgti",null,v,{active:m(f).active}),F(p,m(f).name)}),P(h,g)}),P(u,d)},c=u=>{var d=ip();P(u,d)};H(a,u=>{t.items.length?u(l):u(c,-1)})}P(s,o)},$$slots:{default:!0}})},$$slots:{default:!0}}),et()}var sp=L('
                    • '),rp=L('
                        '),op=L('
                        None.
                        ');function ap(e,t){tt(t,!0),pn(e,{children:(n,i)=>{Rr(n,{get title(){return`Registered Projects (${t.projects.length??""})`},open:!0,children:(s,r)=>{var o=Bt(),a=st(o);{var l=u=>{var d=rp();Mt(d,21,()=>t.projects,h=>h.path,(h,f)=>{var g=sp();let v;var p=w(g),_=w(p),b=k(p,2),x=w(b);W(()=>{v=At(g,1,"project svelte-19jixoo",null,v,{active:m(f).is_active}),F(_,m(f).name),F(x,m(f).path)}),P(h,g)}),P(u,d)},c=u=>{var d=op();P(u,d)};H(a,u=>{t.projects.length?u(l):u(c,-1)})}P(s,o)},$$slots:{default:!0}})},$$slots:{default:!0}}),et()}zh();var lp=L('
                        ');function Hs(e){var t=lp();P(e,t)}var cp=L('
                        '),up=L('
                        '),dp=L('
                        No queued executions.
                        '),hp=L(''),fp=L(" ",1);function gp(e,t){tt(t,!0);let n=Gt(t,"cancelError",3,"");const i=Y(()=>t.items.filter(u=>u.logged));var s=fp(),r=st(s);{var o=u=>{var d=up();Mt(d,21,()=>m(i),h=>h.task_id,(h,f)=>{var g=cp();let v;var p=w(g);{var _=S=>{Hs(S)};H(p,S=>{m(f).is_running&&S(_)})}var b=k(p,2),x=w(b),y=k(b,2);W(()=>{v=At(g,1,"execution-item svelte-1wrcimm",null,v,{running:m(f).is_running}),F(x,m(f).name),_t(y,"aria-label",`Cancel ${m(f).name??""}`)}),K("click",y,()=>t.oncancelexecution(m(f))),P(h,g)}),P(u,d)},a=u=>{var d=dp();P(u,d)};H(r,u=>{m(i).length?u(o):u(a,-1)})}var l=k(r,2);{var c=u=>{var d=hp(),h=w(d);W(()=>F(h,n())),P(u,d)};H(l,u=>{n()&&u(c)})}P(e,s),et()}Et(["click"]);var pp=L('
                        '),vp=L('
                        None yet.
                        ');function mp(e,t){tt(t,!0);const n=Y(()=>t.execution?t.execution.is_running?"Running":t.execution.finished_successfully?"Succeeded":"Failed":""),i=Y(()=>t.execution?t.execution.is_running?"…":t.execution.finished_successfully?"✓":"✗":"");var s=Bt(),r=st(s);{var o=l=>{var c=pp();let u;var d=w(c),h=w(d),f=k(d,2),g=w(f),v=w(g),p=k(g,2),_=w(p),b=k(f,2),x=w(b);W(()=>{u=At(c,1,"last-exec svelte-1772y66",null,u,{ok:t.execution.finished_successfully&&!t.execution.is_running,fail:!t.execution.finished_successfully&&!t.execution.is_running,running:t.execution.is_running}),F(h,m(i)),F(v,m(n)),F(_,t.execution.name),F(x,`#${t.execution.task_id??""}`)}),P(l,c)},a=l=>{var c=vp();P(l,c)};H(r,l=>{t.execution?l(o):l(a,-1)})}P(e,s),et()}var _p=L('
                        '),bp=L('
                        ');function xp(e,t){var n=bp();Mt(n,21,()=>t.items,i=>i.task_id,(i,s)=>{var r=_p();let o;var a=w(r),l=w(a),c=k(a,2),u=w(c),d=k(c,2),h=w(d);W(()=>{o=At(r,1,"cancelled-item svelte-1re8sys",null,o,{abandoned:m(s).is_running}),F(l,m(s).is_running?"!":"✕"),F(u,m(s).name),F(h,`${m(s).is_running?"abandoned · ":""}#${m(s).task_id??""}`)}),P(i,r)}),P(e,n)}function yp(e){return Object.entries(e).sort((t,n)=>n[0].localeCompare(t[0]))}var wp=L('
                        '),kp=L(`

                        What's New

                        `);function Sp(e,t){tt(t,!0);let n=j(mt([]));async function i(){O(n,yp((await wg()).news),!0)}dn(()=>{i()});async function s(l){await kg(l),O(n,m(n).filter(([c])=>c!==l),!0)}var r=Bt(),o=st(r);{var a=l=>{var c=kp(),u=k(w(c),2);Mt(u,17,()=>m(n),([d,h])=>d,(d,h)=>{var f=Y(()=>Xc(m(h),2));let g=()=>m(f)[0],v=()=>m(f)[1];var p=wp(),_=w(p);ju(_,v,!0);var b=k(_,2);K("click",b,()=>s(g())),P(d,p)}),P(l,c)};H(o,l=>{m(n).length&&l(a)})}P(e,r),et()}Et(["click"]);function Mp(e){return e.startsWith("DEBUG")?"debug":e.startsWith("INFO")?"info":e.startsWith("WARNING")?"warning":e.startsWith("ERROR")?"error":"info"}function pl(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function Pp(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Cp(e,t){const n=pl(e);if(t.length===0)return n;const i=[...t].sort((o,a)=>a.length-o.length),s=new Map(i.map(o=>[o.toLowerCase(),o])),r=new RegExp(`\\b(?:${i.map(Pp).join("|")})\\b`,"gi");return n.replace(r,o=>{const a=s.get(o.toLowerCase())??o;return`${pl(a)}`})}const Ap=new Intl.NumberFormat("en-US");function wn(e){return Ap.format(e)}function sn(e){if(e<1)return"<1 ms";if(e<1e3)return`${Math.round(e)} ms`;const t=e/1e3;if(t<60)return`${t.toFixed(1)} s`;const n=t/60;return n<60?`${n.toFixed(1)} m`:`${(n/60).toFixed(1)} h`}function Tp(e){return e<1e3?String(e):e<1e6?`${(e/1e3).toFixed(1)}k`:`${(e/1e6).toFixed(2)}M`}function Dp(e,t){if(e.length===0)return 0;const n=[...e].sort((r,o)=>r-o),i=Math.max(0,Math.min(100,t)),s=Math.floor(i/100*(n.length-1));return n[s]}var Ep=L('
                        Calls
                        Tokens
                        Time
                        Errors
                        ');function Op(e,t){tt(t,!0);const n=Y(()=>t.totals&&t.totals.num_calls>0?t.totals.num_errors/t.totals.num_calls*100:0),i=Y(()=>t.totals&&t.totals.num_calls>0?t.totals.total_duration_ms/t.totals.num_calls:0);var s=Ep(),r=w(s),o=k(w(r),2),a=w(o),l=k(o,2),c=w(l),u=k(r,2),d=k(w(u),2),h=w(d),f=k(d,2),g=w(f),v=k(u,2),p=k(w(v),2),_=w(p),b=k(p,2),x=w(b),y=k(v,2),S=k(w(y),2),D=w(S),T=k(S,2),A=w(T);W((E,R,I,C,V,q,N)=>{F(a,E),F(c,R),F(h,I),F(g,t.totals?"total":""),F(_,C),F(x,V),F(D,q),F(A,N)},[()=>t.totals?wn(t.totals.num_calls):"—",()=>t.totals?`error rate ${m(n).toFixed(2)} %`:"",()=>t.totals?Tp(t.totals.total_tokens):"—",()=>t.totals?sn(t.totals.total_duration_ms):"—",()=>t.totals?`avg ${sn(m(i))} / call`:"",()=>t.totals?wn(t.totals.num_errors):"—",()=>t.totals?`${m(n).toFixed(2)} % rate`:""]),P(e,s),et()}var Lp=L('×'),Rp=L('
                      • '),Ip=L('
                      • No matches
                      • '),Fp=L('
                        '),zp=L('
                        ');function jp(e,t){tt(t,!0);let n=Gt(t,"placeholder",3,"All"),i=j(!1),s=j(""),r=j(0),o=j(void 0),a=j(void 0);const l=Y(()=>t.options.filter(A=>A.label.toLowerCase().includes(m(s).toLowerCase())));function c(){if(O(i,!m(i)),m(i)){const A=m(l).findIndex(E=>E.value===t.value);O(r,A>=0?A:0,!0),queueMicrotask(()=>{var E;return(E=m(o))==null?void 0:E.focus()})}}function u(A){var R;const E=m(l)[A];E&&((R=t.onChange)==null||R.call(t,E.value),O(i,!1),O(s,""))}function d(A){var E;A.stopPropagation(),(E=t.onChange)==null||E.call(t,null)}function h(A){A.key==="ArrowDown"?(A.preventDefault(),O(r,Math.min(m(l).length-1,m(r)+1),!0)):A.key==="ArrowUp"?(A.preventDefault(),O(r,Math.max(0,m(r)-1),!0)):A.key==="Enter"?(A.preventDefault(),u(m(r))):A.key==="Escape"&&O(i,!1)}function f(A){m(a)&&(m(a).contains(A.target)||O(i,!1))}be(()=>{if(m(i))return document.addEventListener("mousedown",f),()=>document.removeEventListener("mousedown",f)});const g=Y(()=>{var A;return t.value?((A=t.options.find(E=>E.value===t.value))==null?void 0:A.label)??t.value:n()});var v=zp();let p;var _=w(v),b=w(_),x=w(b),y=k(b,2);{var S=A=>{var E=Lp();K("click",E,d),K("keydown",E,R=>R.key==="Enter"&&d(R)),P(A,E)};H(y,A=>{t.value!==null&&A(S)})}var D=k(_,2);{var T=A=>{var E=Fp(),R=w(E);Bi(R,N=>O(o,N),()=>m(o));var I=k(R,2),C=w(I);Mt(C,19,()=>m(l),N=>N.value,(N,X,G)=>{var B=Rp();let U;var ct=w(B),bt=w(ct),dt=k(ct,2),vt=w(dt);W(()=>{_t(B,"aria-selected",m(X).value===t.value),U=At(B,1,"svelte-torfu5",null,U,{highlight:m(G)===m(r)}),F(bt,m(X).value===t.value?"✓":""),F(vt,m(X).label)}),Ea("mouseenter",B,()=>O(r,m(G),!0)),K("click",B,()=>u(m(G))),K("keydown",B,Ot=>Ot.key==="Enter"&&u(m(G))),P(N,B)});var V=k(C,2);{var q=N=>{var X=Ip();P(N,X)};H(V,N=>{m(l).length===0&&N(q)})}K("keydown",R,h),Ni(R,()=>m(s),N=>O(s,N)),P(A,E)};H(D,A=>{m(i)&&A(T)})}Bi(v,A=>O(a,A),()=>m(a)),W(()=>{p=At(v,1,"root svelte-torfu5",null,p,{active:t.value!==null}),_t(_,"aria-expanded",m(i)),F(x,m(g))}),K("click",_,c),P(e,v),et()}Et(["click","keydown"]);var Np=L('
                        error:
                        '),Bp=L(''),Vp=L('
                        (server-truncated at 8 KB)
                        '),Wp=L(''),Hp=L('
                        (server-truncated at 8 KB)
                        '),qp=L('
                        seq:
                        started:
                        duration:
                        input:
                         
                        output:
                         
                        '),Yp=L('
                      • ');function Up(e,t){tt(t,!0);let n=j(!1),i=j(!1),s=j(!1);const r=Y(()=>new Date(t.record.started_at*1e3).toISOString().split("T")[1].slice(0,12)),o=Y(()=>t.record.success?"ok":"err");var a=Yp();let l;var c=w(a),u=w(c),d=w(u),h=k(u,2),f=w(h),g=k(h,2),v=w(g),p=k(g,2),_=w(p),b=k(p,2),x=w(b),y=k(c,2);{var S=D=>{var T=qp(),A=w(T),E=k(w(A)),R=k(A,2),I=k(w(R)),C=k(R,2),V=k(w(C)),q=k(C,2);{var N=St=>{var ft=Np(),It=k(w(ft));W(()=>F(It,` ${t.record.error_message??""}`)),P(St,ft)};H(q,St=>{t.record.error_message&&St(N)})}var X=k(q,2),G=k(w(X),2),B=w(G),U=k(G,2);{var ct=St=>{var ft=Bp(),It=w(ft);W(()=>F(It,m(i)?"Show less":"Show full")),K("click",ft,()=>O(i,!m(i))),P(St,ft)};H(U,St=>{t.record.input_preview.length>=400&&St(ct)})}var bt=k(U,2);{var dt=St=>{var ft=Vp();P(St,ft)};H(bt,St=>{t.record.input_truncated&&St(dt)})}var vt=k(X,2),Ot=k(w(vt),2),Ke=w(Ot),oe=k(Ot,2);{var co=St=>{var ft=Wp(),It=w(ft);W(()=>F(It,m(s)?"Show less":"Show full")),K("click",ft,()=>O(s,!m(s))),P(St,ft)};H(oe,St=>{t.record.output_preview.length>=400&&St(co)})}var Xs=k(oe,2);{var uo=St=>{var ft=Hp();P(St,ft)};H(Xs,St=>{t.record.output_truncated&&St(uo)})}W((St,ft,It,ke)=>{F(E,` ${t.record.seq??""}`),F(I,` ${St??""}`),F(V,` ${ft??""}`),F(B,It),F(Ke,ke)},[()=>new Date(t.record.started_at*1e3).toISOString(),()=>sn(t.record.duration_ms),()=>m(i)||t.record.input_preview.length<400?t.record.input_preview:t.record.input_preview.slice(0,400)+"…",()=>m(s)||t.record.output_preview.length<400?t.record.output_preview:t.record.output_preview.slice(0,400)+"…"]),P(D,T)};H(y,D=>{m(n)&&D(S)})}W(D=>{l=At(a,1,"row svelte-1112a67",null,l,{expanded:m(n)}),_t(c,"aria-expanded",m(n)),F(d,m(n)?"▾":"▸"),F(f,m(r)),F(v,t.record.tool),F(_,D),At(b,1,`status ${m(o)??""}`,"svelte-1112a67"),F(x,t.record.success?"ok":"ERR")},[()=>sn(t.record.duration_ms)]),K("click",c,()=>O(n,!m(n))),P(e,a),et()}Et(["click"]);var Xp=L(''),Kp=L(''),Gp=L('
                      • No calls yet.
                      • '),Zp=L('

                        Tool Call Timeline

                        ');function Jp(e,t){tt(t,!0);const n=Y(()=>t.toolNames.map(B=>({value:B,label:B}))),i=Y(()=>Math.max(1,Math.ceil(t.store.records.length/t.store.pageSize))),s=Y(()=>(t.store.page-1)*t.store.pageSize),r=Y(()=>t.store.records.slice(m(s),m(s)+t.store.pageSize));var o=Zp(),a=w(o),l=k(w(a),2),c=w(l);jp(c,{get options(){return m(n)},get value(){return t.store.filter},placeholder:"All tools",onChange:B=>t.store.setFilter(B)});var u=k(c,2),d=w(u),h=k(u,2),f=k(a,2);{var g=B=>{var U=Xp(),ct=w(U);W(bt=>F(ct,`(${bt??""} calls while paused — view truncated)`),[()=>t.store.pausedGap.toLocaleString()]),P(B,U)};H(f,B=>{t.store.pausedGap&&B(g)})}var v=k(f,2);{var p=B=>{var U=Kp();P(B,U)};H(v,B=>{t.store.error&&B(p)})}var _=k(v,2),b=w(_),x=k(w(b)),y=w(x);y.value=y.__value="25";var S=k(y);S.value=S.__value="50";var D=k(S);D.value=D.__value="100";var T;La(x);var A=k(b,4),E=k(A,2),R=k(E,2),I=w(R),C=k(R,2),V=k(C,2),q=k(_,2),N=w(q);Mt(N,17,()=>m(r),B=>B.seq,(B,U)=>{Up(B,{get record(){return m(U)}})});var X=k(N,2);{var G=B=>{var U=Gp();P(B,U)};H(X,B=>{m(r).length===0&&B(G)})}W(B=>{_t(u,"aria-label",t.store.paused?"Resume":"Pause"),F(d,t.store.paused?"▶ Resume":"⏸ Pause"),T!==(T=B)&&(x.value=(x.__value=B)??"",to(x,B)),A.disabled=t.store.page<=1,E.disabled=t.store.page<=1,F(I,`page ${t.store.page??""} of ${m(i)??""}`),C.disabled=t.store.page>=m(i),V.disabled=t.store.page>=m(i)},[()=>String(t.store.pageSize)]),K("click",u,()=>t.store.togglePause()),K("click",h,()=>t.store.clearView()),K("change",x,B=>t.store.setPageSize(Number(B.target.value))),K("click",A,()=>t.store.setPage(1)),K("click",E,()=>t.store.setPage(t.store.page-1)),K("click",C,()=>t.store.setPage(t.store.page+1)),K("click",V,()=>t.store.setPage(m(i))),P(e,o),et()}Et(["click","change"]);var Qp=L('
                        ',1);function $p(e,t){tt(t,!0);const n=Y(()=>Lr.data),i=Y(()=>{var u;const l=Object.keys(((u=m(n))==null?void 0:u.tool_stats_summary)??{}),c=new Set(di.records.map(d=>d.tool));for(const d of l)c.add(d);return[...c].sort()});var s=Bt(),r=st(s);{var o=l=>{Hs(l)},a=l=>{var c=Qp(),u=st(c);Op(u,{get totals(){return m(n).tool_stats_totals}});var d=k(u,2);Jp(d,{get store(){return di},get toolNames(){return m(i)}});var h=k(d,2),f=w(h),g=w(f);Sp(g,{});var v=k(g,2);pn(v,{title:"Current Configuration",children:(I,C)=>{Kg(I,{get data(){return m(n)},get onaddlanguage(){return t.onaddlanguage},get onremovelanguage(){return t.onremovelanguage},get oneditconfig(){return t.oneditconfig},get onopenmemory(){return t.onopenmemory},get oncreatememory(){return t.oncreatememory},get ondeletememory(){return t.ondeletememory}})},$$slots:{default:!0}});var p=k(v,2);pn(p,{title:"Tool Usage",children:(I,C)=>{Qg(I,{get stats(){return m(n).tool_stats_summary}})},$$slots:{default:!0}});var _=k(p,2);pn(_,{title:"Executions Queue",children:(I,C)=>{gp(I,{get items(){return Ve.queued},get cancelError(){return Ve.cancelError},get oncancelexecution(){return t.oncancelexecution}})},$$slots:{default:!0}});var b=k(_,2);{var x=I=>{pn(I,{title:"Cancelled Executions",children:(C,V)=>{xp(C,{get items(){return Ve.cancelled}})},$$slots:{default:!0}})};H(b,I=>{Ve.cancelled.length&&I(x)})}var y=k(b,2);pn(y,{title:"Last Execution",children:(I,C)=>{mp(I,{get execution(){return Ve.last}})},$$slots:{default:!0}});var S=k(f,2),D=w(S);ap(D,{get projects(){return m(n).registered_projects}});var T=k(D,2);{let I=Y(()=>m(n).available_tools.filter(C=>!C.is_active).map(C=>({name:C.name})));yo(T,{title:"Available Tools (Disabled)",get items(){return m(I)}})}var A=k(T,2);{let I=Y(()=>m(n).available_modes.map(C=>({name:C.name,active:C.is_active})));yo(A,{title:"Available Modes",get items(){return m(I)}})}var E=k(A,2);{let I=Y(()=>m(n).available_contexts.map(C=>({name:C.name,active:C.is_active})));yo(E,{title:"Available Contexts",get items(){return m(I)}})}var R=k(E,2);pn(R,{children:(I,C)=>{Wu(I,{target:"gold"})},$$slots:{default:!0}}),P(l,c)};H(r,l=>{m(n)?l(a,-1):l(o)})}P(e,s),et()}var tv=L('
                        ');function ev(e,t){tt(t,!0);let n=j(!1);const i=Y(()=>t.lines.length===0);async function s(){try{await navigator.clipboard.writeText(t.lines.join(` +`)),O(n,!0),setTimeout(()=>O(n,!1),1e3)}catch{}}function r(){const d=new Blob([t.lines.join(` +`)],{type:"text/plain"}),h=URL.createObjectURL(d),f=document.createElement("a");f.href=h,f.download="serena-logs.txt",f.click(),setTimeout(()=>URL.revokeObjectURL(h),0)}var o=tv(),a=w(o),l=w(a),c=k(a,2),u=k(c,2);W(()=>{a.disabled=m(i),F(l,m(n)?"✓ copied":"copy logs"),c.disabled=m(i),u.disabled=m(i)}),K("click",a,s),K("click",c,r),K("click",u,function(...d){var h;(h=t.onclear)==null||h.apply(this,d)}),P(e,o),et()}Et(["click"]);var nv=L("
                        "),iv=L('
                        ');function sv(e,t){tt(t,!0);let n=j(null),i=!1;be(()=>{if(t.lines.length,!m(n)||t.lines.length===0)return;if(!i){i=!0,queueMicrotask(()=>{m(n)&&(m(n).scrollTop=m(n).scrollHeight)});return}m(n).scrollHeight-m(n).scrollTop-m(n).clientHeight<40&&queueMicrotask(()=>{m(n)&&(m(n).scrollTop=m(n).scrollHeight)})});var s=iv();Mt(s,21,()=>t.lines,zu,(r,o)=>{var a=nv();ju(a,()=>Cp(m(o),t.toolNames),!0),W(l=>At(a,1,`log-line ${l??""}`,"svelte-1f7p4v8"),[()=>Mp(m(o))]),P(r,a)}),Bi(s,r=>O(n,r),()=>m(n)),P(e,s),et()}var rv=L(" ",1);function ov(e,t){tt(t,!0);let n=j(mt([]));dn(()=>{(async()=>O(n,(await og()).tool_names,!0))()});var i=rv(),s=st(i);ev(s,{get lines(){return yr.lines},onclear:()=>yr.clear()});var r=k(s,2);sv(r,{get lines(){return yr.lines},get toolNames(){return m(n)}}),P(e,i),et()}function av(){let e=j(mt({})),t=j("unknown"),n=j("calls"),i=j(null);return{get stats(){return m(e)},get estimator(){return m(t)},get sortKey(){return m(n)},get drillTool(){return m(i)},setSortKey(s){O(n,s,!0)},setDrillTool(s){O(i,s,!0)},async refresh(){O(e,(await ag()).stats,!0),O(t,(await cg()).token_count_estimator_name,!0)},async clear(){await lg(),O(e,{},!0),O(i,null)}}}const Lt=av();function lv(e,t){return Object.entries(e).sort((n,i)=>{const s=n[1][t]??0;return(i[1][t]??0)-s})}const cv=.04;function wo(e,t){const n=lv(e,t);return{type:"pie",data:{labels:n.map(([i])=>i),datasets:[{data:n.map(([,i])=>i[t]??0)}]},options:{plugins:{legend:{display:!0,position:"bottom",labels:{}},datalabels:{display:i=>{const s=i.dataset.data,r=s.reduce((o,a)=>o+a,0);return r===0?!1:s[i.dataIndex]/r>=cv},color:"#ffffff",font:{weight:"bold"},formatter:i=>i}}}}}function qu(e,t){const n=i=>{switch(t){case"calls":return i.num_times_called;case"tokens":return(i.input_tokens??0)+(i.output_tokens??0);case"duration_total":return i.total_duration_ms??0;case"duration_avg":return i.num_times_called>0?(i.total_duration_ms??0)/i.num_times_called:0;case"errors":return i.num_errors??0}};return Object.entries(e).sort((i,s)=>n(s[1])-n(i[1])).map(([i])=>i)}function uv(e,t){const n=qu(e,t),i=n.map(r=>{var o;return((o=e[r])==null?void 0:o.input_tokens)??0}),s=n.map(r=>{var o;return((o=e[r])==null?void 0:o.output_tokens)??0});return{type:"bar",data:{labels:n,datasets:[{label:"Input Tokens",data:i,yAxisID:"y"},{label:"Output Tokens",data:s,yAxisID:"y1"}]},options:{responsive:!0,layout:{padding:{bottom:8}},plugins:{legend:{display:!0,labels:{}},datalabels:{display:!1}},scales:{x:{ticks:{maxRotation:35,padding:8}},y:{type:"linear",position:"left",beginAtZero:!0,title:{display:!0,text:"Input Tokens"}},y1:{type:"linear",position:"right",beginAtZero:!0,title:{display:!0,text:"Output Tokens"},grid:{drawOnChartArea:!1}}}}}}function dv(e,t){const n=qu(e,t),i=n.map(r=>{const o=e[r];return!o||o.num_times_called<=0?0:(o.total_duration_ms??0)/o.num_times_called}),s=n.map(r=>{var o;return((o=e[r])==null?void 0:o.max_duration_ms)??0});return{type:"bar",data:{labels:n,datasets:[{label:"Avg (ms)",data:i,yAxisID:"y"},{label:"Max (ms)",data:s,yAxisID:"y1"}]},options:{responsive:!0,maintainAspectRatio:!1,layout:{padding:{bottom:8}},plugins:{legend:{display:!0,labels:{}},datalabels:{display:!1}},scales:{x:{ticks:{maxRotation:35,padding:8}},y:{type:"linear",position:"left",beginAtZero:!0,title:{display:!0,text:"Avg (ms)"}},y1:{type:"linear",position:"right",beginAtZero:!0,title:{display:!0,text:"Max (ms)"},grid:{drawOnChartArea:!1}}}}}}function hv(e,t,n){const i=Math.floor(t/60)*60,s=i-n*60,r=n+1,o=Array.from({length:r},(a,l)=>({start:s+l*60,count:0,byTool:{}}));for(const a of e){if(a.started_at=i+60)continue;const l=Math.floor((a.started_at-s)/60);l<0||l>=r||(o[l].count+=1,o[l].byTool[a.tool]=(o[l].byTool[a.tool]??0)+1)}return o}function fv(e,t,n,i,s){const r=hv(e,t,n),o=r.map(a=>new Date(a.start*1e3).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"}));return i?{type:"line",data:{labels:o,datasets:s.map(a=>({label:a,data:r.map(l=>l.byTool[a]??0),fill:!0}))},options:{responsive:!0,maintainAspectRatio:!1,plugins:{legend:{display:!0,labels:{}},datalabels:{display:!1}},scales:{x:{stacked:!0,ticks:{maxRotation:0,padding:4}},y:{stacked:!0,beginAtZero:!0,ticks:{maxTicksLimit:8}}}}}:{type:"line",data:{labels:o,datasets:[{label:"Calls / min",data:r.map(a=>a.count),fill:!1}]},options:{responsive:!0,maintainAspectRatio:!1,plugins:{legend:{display:!0,labels:{}},datalabels:{display:!1}},scales:{x:{ticks:{maxRotation:0,padding:4}},y:{beginAtZero:!0,ticks:{maxTicksLimit:8}}}}}}/*! + * @kurkle/color v0.3.4 + * https://github.com/kurkle/color#readme + * (c) 2024 Jukka Kurkela + * Released under the MIT License + */function qs(e){return e+.5|0}const kn=(e,t,n)=>Math.max(Math.min(e,n),t);function os(e){return kn(qs(e*2.55),0,255)}function On(e){return kn(qs(e*255),0,255)}function Qe(e){return kn(qs(e/2.55)/100,0,1)}function vl(e){return kn(qs(e*100),0,100)}const he={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},ea=[..."0123456789ABCDEF"],gv=e=>ea[e&15],pv=e=>ea[(e&240)>>4]+ea[e&15],Zs=e=>(e&240)>>4===(e&15),vv=e=>Zs(e.r)&&Zs(e.g)&&Zs(e.b)&&Zs(e.a);function mv(e){var t=e.length,n;return e[0]==="#"&&(t===4||t===5?n={r:255&he[e[1]]*17,g:255&he[e[2]]*17,b:255&he[e[3]]*17,a:t===5?he[e[4]]*17:255}:(t===7||t===9)&&(n={r:he[e[1]]<<4|he[e[2]],g:he[e[3]]<<4|he[e[4]],b:he[e[5]]<<4|he[e[6]],a:t===9?he[e[7]]<<4|he[e[8]]:255})),n}const _v=(e,t)=>e<255?t(e):"";function bv(e){var t=vv(e)?gv:pv;return e?"#"+t(e.r)+t(e.g)+t(e.b)+_v(e.a,t):void 0}const xv=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Yu(e,t,n){const i=t*Math.min(n,1-n),s=(r,o=(r+e/30)%12)=>n-i*Math.max(Math.min(o-3,9-o,1),-1);return[s(0),s(8),s(4)]}function yv(e,t,n){const i=(s,r=(s+e/60)%6)=>n-n*t*Math.max(Math.min(r,4-r,1),0);return[i(5),i(3),i(1)]}function wv(e,t,n){const i=Yu(e,1,.5);let s;for(t+n>1&&(s=1/(t+n),t*=s,n*=s),s=0;s<3;s++)i[s]*=1-t-n,i[s]+=t;return i}function kv(e,t,n,i,s){return e===s?(t-n)/i+(t.5?u/(2-r-o):u/(r+o),l=kv(n,i,s,u,r),l=l*60+.5),[l|0,c||0,a]}function za(e,t,n,i){return(Array.isArray(t)?e(t[0],t[1],t[2]):e(t,n,i)).map(On)}function ja(e,t,n){return za(Yu,e,t,n)}function Sv(e,t,n){return za(wv,e,t,n)}function Mv(e,t,n){return za(yv,e,t,n)}function Uu(e){return(e%360+360)%360}function Pv(e){const t=xv.exec(e);let n=255,i;if(!t)return;t[5]!==i&&(n=t[6]?os(+t[5]):On(+t[5]));const s=Uu(+t[2]),r=+t[3]/100,o=+t[4]/100;return t[1]==="hwb"?i=Sv(s,r,o):t[1]==="hsv"?i=Mv(s,r,o):i=ja(s,r,o),{r:i[0],g:i[1],b:i[2],a:n}}function Cv(e,t){var n=Fa(e);n[0]=Uu(n[0]+t),n=ja(n),e.r=n[0],e.g=n[1],e.b=n[2]}function Av(e){if(!e)return;const t=Fa(e),n=t[0],i=vl(t[1]),s=vl(t[2]);return e.a<255?`hsla(${n}, ${i}%, ${s}%, ${Qe(e.a)})`:`hsl(${n}, ${i}%, ${s}%)`}const ml={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},_l={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};function Tv(){const e={},t=Object.keys(_l),n=Object.keys(ml);let i,s,r,o,a;for(i=0;i>16&255,r>>8&255,r&255]}return e}let Js;function Dv(e){Js||(Js=Tv(),Js.transparent=[0,0,0,0]);const t=Js[e.toLowerCase()];return t&&{r:t[0],g:t[1],b:t[2],a:t.length===4?t[3]:255}}const Ev=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;function Ov(e){const t=Ev.exec(e);let n=255,i,s,r;if(t){if(t[7]!==i){const o=+t[7];n=t[8]?os(o):kn(o*255,0,255)}return i=+t[1],s=+t[3],r=+t[5],i=255&(t[2]?os(i):kn(i,0,255)),s=255&(t[4]?os(s):kn(s,0,255)),r=255&(t[6]?os(r):kn(r,0,255)),{r:i,g:s,b:r,a:n}}}function Lv(e){return e&&(e.a<255?`rgba(${e.r}, ${e.g}, ${e.b}, ${Qe(e.a)})`:`rgb(${e.r}, ${e.g}, ${e.b})`)}const ko=e=>e<=.0031308?e*12.92:Math.pow(e,1/2.4)*1.055-.055,bi=e=>e<=.04045?e/12.92:Math.pow((e+.055)/1.055,2.4);function Rv(e,t,n){const i=bi(Qe(e.r)),s=bi(Qe(e.g)),r=bi(Qe(e.b));return{r:On(ko(i+n*(bi(Qe(t.r))-i))),g:On(ko(s+n*(bi(Qe(t.g))-s))),b:On(ko(r+n*(bi(Qe(t.b))-r))),a:e.a+n*(t.a-e.a)}}function Qs(e,t,n){if(e){let i=Fa(e);i[t]=Math.max(0,Math.min(i[t]+i[t]*n,t===0?360:1)),i=ja(i),e.r=i[0],e.g=i[1],e.b=i[2]}}function Xu(e,t){return e&&Object.assign(t||{},e)}function bl(e){var t={r:0,g:0,b:0,a:255};return Array.isArray(e)?e.length>=3&&(t={r:e[0],g:e[1],b:e[2],a:255},e.length>3&&(t.a=On(e[3]))):(t=Xu(e,{r:0,g:0,b:0,a:1}),t.a=On(t.a)),t}function Iv(e){return e.charAt(0)==="r"?Ov(e):Pv(e)}class Ss{constructor(t){if(t instanceof Ss)return t;const n=typeof t;let i;n==="object"?i=bl(t):n==="string"&&(i=mv(t)||Dv(t)||Iv(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=Xu(this._rgb);return t&&(t.a=Qe(t.a)),t}set rgb(t){this._rgb=bl(t)}rgbString(){return this._valid?Lv(this._rgb):void 0}hexString(){return this._valid?bv(this._rgb):void 0}hslString(){return this._valid?Av(this._rgb):void 0}mix(t,n){if(t){const i=this.rgb,s=t.rgb;let r;const o=n===r?.5:n,a=2*o-1,l=i.a-s.a,c=((a*l===-1?a:(a+l)/(1+a*l))+1)/2;r=1-c,i.r=255&c*i.r+r*s.r+.5,i.g=255&c*i.g+r*s.g+.5,i.b=255&c*i.b+r*s.b+.5,i.a=o*i.a+(1-o)*s.a,this.rgb=i}return this}interpolate(t,n){return t&&(this._rgb=Rv(this._rgb,t._rgb,n)),this}clone(){return new Ss(this.rgb)}alpha(t){return this._rgb.a=On(t),this}clearer(t){const n=this._rgb;return n.a*=1-t,this}greyscale(){const t=this._rgb,n=qs(t.r*.3+t.g*.59+t.b*.11);return t.r=t.g=t.b=n,this}opaquer(t){const n=this._rgb;return n.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return Qs(this._rgb,2,t),this}darken(t){return Qs(this._rgb,2,-t),this}saturate(t){return Qs(this._rgb,1,t),this}desaturate(t){return Qs(this._rgb,1,-t),this}rotate(t){return Cv(this._rgb,t),this}}/*! + * Chart.js v4.5.1 + * https://www.chartjs.org + * (c) 2025 Chart.js Contributors + * Released under the MIT License + */function Ge(){}const Fv=(()=>{let e=0;return()=>e++})();function it(e){return e==null}function yt(e){if(Array.isArray&&Array.isArray(e))return!0;const t=Object.prototype.toString.call(e);return t.slice(0,7)==="[object"&&t.slice(-6)==="Array]"}function ot(e){return e!==null&&Object.prototype.toString.call(e)==="[object Object]"}function Tt(e){return(typeof e=="number"||e instanceof Number)&&isFinite(+e)}function le(e,t){return Tt(e)?e:t}function Q(e,t){return typeof e>"u"?t:e}const zv=(e,t)=>typeof e=="string"&&e.endsWith("%")?parseFloat(e)/100:+e/t,Ku=(e,t)=>typeof e=="string"&&e.endsWith("%")?parseFloat(e)/100*t:+e;function pt(e,t,n){if(e&&typeof e.call=="function")return e.apply(n,t)}function gt(e,t,n,i){let s,r,o;if(yt(e))for(r=e.length,s=0;se,x:e=>e.x,y:e=>e.y};function Bv(e){const t=e.split("."),n=[];let i="";for(const s of t)i+=s,i.endsWith("\\")?i=i.slice(0,-1)+".":(n.push(i),i="");return n}function Vv(e){const t=Bv(e);return n=>{for(const i of t){if(i==="")break;n=n&&n[i]}return n}}function Fn(e,t){return(xl[t]||(xl[t]=Vv(t)))(e)}function Na(e){return e.charAt(0).toUpperCase()+e.slice(1)}const Ms=e=>typeof e<"u",zn=e=>typeof e=="function",yl=(e,t)=>{if(e.size!==t.size)return!1;for(const n of e)if(!t.has(n))return!1;return!0};function Wv(e){return e.type==="mouseup"||e.type==="click"||e.type==="contextmenu"}const ut=Math.PI,wt=2*ut,Hv=wt+ut,zr=Number.POSITIVE_INFINITY,qv=ut/180,Rt=ut/2,Bn=ut/4,wl=ut*2/3,Sn=Math.log10,He=Math.sign;function gs(e,t,n){return Math.abs(e-t)s-r).pop(),t}function Uv(e){return typeof e=="symbol"||typeof e=="object"&&e!==null&&!(Symbol.toPrimitive in e||"toString"in e||"valueOf"in e)}function Vi(e){return!Uv(e)&&!isNaN(parseFloat(e))&&isFinite(e)}function Xv(e,t){const n=Math.round(e);return n-t<=e&&n+t>=e}function Zu(e,t,n){let i,s,r;for(i=0,s=e.length;il&&c=Math.min(t,n)-i&&e<=Math.max(t,n)+i}function Va(e,t,n){n=n||(o=>e[o]1;)r=s+i>>1,n(r)?s=r:i=r;return{lo:s,hi:i}}const on=(e,t,n,i)=>Va(e,n,i?s=>{const r=e[s][t];return re[s][t]Va(e,n,i=>e[i][t]>=n);function Jv(e,t,n){let i=0,s=e.length;for(;ii&&e[s-1]>n;)s--;return i>0||s{const i="_onData"+Na(n),s=e[n];Object.defineProperty(e,n,{configurable:!0,enumerable:!1,value(...r){const o=s.apply(this,r);return e._chartjs.listeners.forEach(a=>{typeof a[i]=="function"&&a[i](...r)}),o}})})}function Ml(e,t){const n=e._chartjs;if(!n)return;const i=n.listeners,s=i.indexOf(t);s!==-1&&i.splice(s,1),!(i.length>0)&&(Qu.forEach(r=>{delete e[r]}),delete e._chartjs)}function $u(e){const t=new Set(e);return t.size===e.length?e:Array.from(t)}const td=(function(){return typeof window>"u"?function(e){return e()}:window.requestAnimationFrame})();function ed(e,t){let n=[],i=!1;return function(...s){n=s,i||(i=!0,td.call(window,()=>{i=!1,e.apply(t,n)}))}}function $v(e,t){let n;return function(...i){return t?(clearTimeout(n),n=setTimeout(e,t,i)):e.apply(this,i),t}}const Wa=e=>e==="start"?"left":e==="end"?"right":"center",Ut=(e,t,n)=>e==="start"?t:e==="end"?n:(t+n)/2,tm=(e,t,n,i)=>e===(i?"left":"right")?n:e==="center"?(t+n)/2:t;function nd(e,t,n){const i=t.length;let s=0,r=i;if(e._sorted){const{iScale:o,vScale:a,_parsed:l}=e,c=e.dataset&&e.dataset.options?e.dataset.options.spanGaps:null,u=o.axis,{min:d,max:h,minDefined:f,maxDefined:g}=o.getUserBounds();if(f){if(s=Math.min(on(l,u,d).lo,n?i:on(t,u,o.getPixelForValue(d)).lo),c){const v=l.slice(0,s+1).reverse().findIndex(p=>!it(p[a.axis]));s-=Math.max(0,v)}s=Wt(s,0,i-1)}if(g){let v=Math.max(on(l,o.axis,h,!0).hi+1,n?0:on(t,u,o.getPixelForValue(h),!0).hi+1);if(c){const p=l.slice(v-1).findIndex(_=>!it(_[a.axis]));v+=Math.max(0,p)}r=Wt(v,s,i)-s}else r=i-s}return{start:s,count:r}}function id(e){const{xScale:t,yScale:n,_scaleRanges:i}=e,s={xmin:t.min,xmax:t.max,ymin:n.min,ymax:n.max};if(!i)return e._scaleRanges=s,!0;const r=i.xmin!==t.min||i.xmax!==t.max||i.ymin!==n.min||i.ymax!==n.max;return Object.assign(i,s),r}const $s=e=>e===0||e===1,Pl=(e,t,n)=>-(Math.pow(2,10*(e-=1))*Math.sin((e-t)*wt/n)),Cl=(e,t,n)=>Math.pow(2,-10*e)*Math.sin((e-t)*wt/n)+1,ps={linear:e=>e,easeInQuad:e=>e*e,easeOutQuad:e=>-e*(e-2),easeInOutQuad:e=>(e/=.5)<1?.5*e*e:-.5*(--e*(e-2)-1),easeInCubic:e=>e*e*e,easeOutCubic:e=>(e-=1)*e*e+1,easeInOutCubic:e=>(e/=.5)<1?.5*e*e*e:.5*((e-=2)*e*e+2),easeInQuart:e=>e*e*e*e,easeOutQuart:e=>-((e-=1)*e*e*e-1),easeInOutQuart:e=>(e/=.5)<1?.5*e*e*e*e:-.5*((e-=2)*e*e*e-2),easeInQuint:e=>e*e*e*e*e,easeOutQuint:e=>(e-=1)*e*e*e*e+1,easeInOutQuint:e=>(e/=.5)<1?.5*e*e*e*e*e:.5*((e-=2)*e*e*e*e+2),easeInSine:e=>-Math.cos(e*Rt)+1,easeOutSine:e=>Math.sin(e*Rt),easeInOutSine:e=>-.5*(Math.cos(ut*e)-1),easeInExpo:e=>e===0?0:Math.pow(2,10*(e-1)),easeOutExpo:e=>e===1?1:-Math.pow(2,-10*e)+1,easeInOutExpo:e=>$s(e)?e:e<.5?.5*Math.pow(2,10*(e*2-1)):.5*(-Math.pow(2,-10*(e*2-1))+2),easeInCirc:e=>e>=1?e:-(Math.sqrt(1-e*e)-1),easeOutCirc:e=>Math.sqrt(1-(e-=1)*e),easeInOutCirc:e=>(e/=.5)<1?-.5*(Math.sqrt(1-e*e)-1):.5*(Math.sqrt(1-(e-=2)*e)+1),easeInElastic:e=>$s(e)?e:Pl(e,.075,.3),easeOutElastic:e=>$s(e)?e:Cl(e,.075,.3),easeInOutElastic(e){return $s(e)?e:e<.5?.5*Pl(e*2,.1125,.45):.5+.5*Cl(e*2-1,.1125,.45)},easeInBack(e){return e*e*((1.70158+1)*e-1.70158)},easeOutBack(e){return(e-=1)*e*((1.70158+1)*e+1.70158)+1},easeInOutBack(e){let t=1.70158;return(e/=.5)<1?.5*(e*e*(((t*=1.525)+1)*e-t)):.5*((e-=2)*e*(((t*=1.525)+1)*e+t)+2)},easeInBounce:e=>1-ps.easeOutBounce(1-e),easeOutBounce(e){return e<1/2.75?7.5625*e*e:e<2/2.75?7.5625*(e-=1.5/2.75)*e+.75:e<2.5/2.75?7.5625*(e-=2.25/2.75)*e+.9375:7.5625*(e-=2.625/2.75)*e+.984375},easeInOutBounce:e=>e<.5?ps.easeInBounce(e*2)*.5:ps.easeOutBounce(e*2-1)*.5+.5};function Ha(e){if(e&&typeof e=="object"){const t=e.toString();return t==="[object CanvasPattern]"||t==="[object CanvasGradient]"}return!1}function Al(e){return Ha(e)?e:new Ss(e)}function So(e){return Ha(e)?e:new Ss(e).saturate(.5).darken(.1).hexString()}const em=["x","y","borderWidth","radius","tension"],nm=["color","borderColor","backgroundColor"];function im(e){e.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),e.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:t=>t!=="onProgress"&&t!=="onComplete"&&t!=="fn"}),e.set("animations",{colors:{type:"color",properties:nm},numbers:{type:"number",properties:em}}),e.describe("animations",{_fallback:"animation"}),e.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>t|0}}}})}function sm(e){e.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})}const Tl=new Map;function rm(e,t){t=t||{};const n=e+JSON.stringify(t);let i=Tl.get(n);return i||(i=new Intl.NumberFormat(e,t),Tl.set(n,i)),i}function Ys(e,t,n){return rm(t,n).format(e)}const sd={values(e){return yt(e)?e:""+e},numeric(e,t,n){if(e===0)return"0";const i=this.chart.options.locale;let s,r=e;if(n.length>1){const c=Math.max(Math.abs(n[0].value),Math.abs(n[n.length-1].value));(c<1e-4||c>1e15)&&(s="scientific"),r=om(e,n)}const o=Sn(Math.abs(r)),a=isNaN(o)?1:Math.max(Math.min(-1*Math.floor(o),20),0),l={notation:s,minimumFractionDigits:a,maximumFractionDigits:a};return Object.assign(l,this.options.ticks.format),Ys(e,i,l)},logarithmic(e,t,n){if(e===0)return"0";const i=n[t].significand||e/Math.pow(10,Math.floor(Sn(e)));return[1,2,3,5,10,15].includes(i)||t>.8*n.length?sd.numeric.call(this,e,t,n):""}};function om(e,t){let n=t.length>3?t[2].value-t[1].value:t[1].value-t[0].value;return Math.abs(n)>=1&&e!==Math.floor(e)&&(n=e-Math.floor(e)),n}var eo={formatters:sd};function am(e){e.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",clip:!0,grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(t,n)=>n.lineWidth,tickColor:(t,n)=>n.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:eo.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),e.route("scale.ticks","color","","color"),e.route("scale.grid","color","","borderColor"),e.route("scale.border","color","","borderColor"),e.route("scale.title","color","","color"),e.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&t!=="callback"&&t!=="parser",_indexable:t=>t!=="borderDash"&&t!=="tickBorderDash"&&t!=="dash"}),e.describe("scales",{_fallback:"scale"}),e.describe("scale.ticks",{_scriptable:t=>t!=="backdropPadding"&&t!=="callback",_indexable:t=>t!=="backdropPadding"})}const hi=Object.create(null),ia=Object.create(null);function vs(e,t){if(!t)return e;const n=t.split(".");for(let i=0,s=n.length;ii.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(i,s)=>So(s.backgroundColor),this.hoverBorderColor=(i,s)=>So(s.borderColor),this.hoverColor=(i,s)=>So(s.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t),this.apply(n)}set(t,n){return Mo(this,t,n)}get(t){return vs(this,t)}describe(t,n){return Mo(ia,t,n)}override(t,n){return Mo(hi,t,n)}route(t,n,i,s){const r=vs(this,t),o=vs(this,i),a="_"+n;Object.defineProperties(r,{[a]:{value:r[n],writable:!0},[n]:{enumerable:!0,get(){const l=this[a],c=o[s];return ot(l)?Object.assign({},c,l):Q(l,c)},set(l){this[a]=l}}})}apply(t){t.forEach(n=>n(this))}}var kt=new lm({_scriptable:e=>!e.startsWith("on"),_indexable:e=>e!=="events",hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[im,sm,am]);function cm(e){return!e||it(e.size)||it(e.family)?null:(e.style?e.style+" ":"")+(e.weight?e.weight+" ":"")+e.size+"px "+e.family}function jr(e,t,n,i,s){let r=t[s];return r||(r=t[s]=e.measureText(s).width,n.push(s)),r>i&&(i=r),i}function um(e,t,n,i){i=i||{};let s=i.data=i.data||{},r=i.garbageCollect=i.garbageCollect||[];i.font!==t&&(s=i.data={},r=i.garbageCollect=[],i.font=t),e.save(),e.font=t;let o=0;const a=n.length;let l,c,u,d,h;for(l=0;ln.length){for(l=0;l0&&e.stroke()}}function an(e,t,n){return n=n||.5,!t||e&&e.x>t.left-n&&e.xt.top-n&&e.y0&&r.strokeColor!=="";let l,c;for(e.save(),e.font=s.string,fm(e,r),l=0;l+e||0;function qa(e,t){const n={},i=ot(t),s=i?Object.keys(t):t,r=ot(e)?i?o=>Q(e[o],e[t[o]]):o=>e[o]:()=>e;for(const o of s)n[o]=bm(r(o));return n}function od(e){return qa(e,{top:"y",right:"x",bottom:"y",left:"x"})}function oi(e){return qa(e,["topLeft","topRight","bottomLeft","bottomRight"])}function Yt(e){const t=od(e);return t.width=t.left+t.right,t.height=t.top+t.bottom,t}function zt(e,t){e=e||{},t=t||kt.font;let n=Q(e.size,t.size);typeof n=="string"&&(n=parseInt(n,10));let i=Q(e.style,t.style);i&&!(""+i).match(mm)&&(console.warn('Invalid font style specified: "'+i+'"'),i=void 0);const s={family:Q(e.family,t.family),lineHeight:_m(Q(e.lineHeight,t.lineHeight),n),size:n,style:i,weight:Q(e.weight,t.weight),string:""};return s.string=cm(s),s}function xt(e,t,n,i){let s,r,o;for(s=0,r=e.length;sn&&a===0?0:a+l;return{min:o(i,-Math.abs(r)),max:o(s,r)}}function jn(e,t){return Object.assign(Object.create(e),t)}function Ya(e,t=[""],n,i,s=()=>e[0]){const r=n||e;typeof i>"u"&&(i=ud("_fallback",e));const o={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:e,_rootScopes:r,_fallback:i,_getTarget:s,override:a=>Ya([a,...e],t,r,i)};return new Proxy(o,{deleteProperty(a,l){return delete a[l],delete a._keys,delete e[0][l],!0},get(a,l){return ld(a,l,()=>Am(l,t,e,a))},getOwnPropertyDescriptor(a,l){return Reflect.getOwnPropertyDescriptor(a._scopes[0],l)},getPrototypeOf(){return Reflect.getPrototypeOf(e[0])},has(a,l){return Ol(a).includes(l)},ownKeys(a){return Ol(a)},set(a,l,c){const u=a._storage||(a._storage=s());return a[l]=u[l]=c,delete a._keys,!0}})}function Wi(e,t,n,i){const s={_cacheable:!1,_proxy:e,_context:t,_subProxy:n,_stack:new Set,_descriptors:ad(e,i),setContext:r=>Wi(e,r,n,i),override:r=>Wi(e.override(r),t,n,i)};return new Proxy(s,{deleteProperty(r,o){return delete r[o],delete e[o],!0},get(r,o,a){return ld(r,o,()=>wm(r,o,a))},getOwnPropertyDescriptor(r,o){return r._descriptors.allKeys?Reflect.has(e,o)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(e,o)},getPrototypeOf(){return Reflect.getPrototypeOf(e)},has(r,o){return Reflect.has(e,o)},ownKeys(){return Reflect.ownKeys(e)},set(r,o,a){return e[o]=a,delete r[o],!0}})}function ad(e,t={scriptable:!0,indexable:!0}){const{_scriptable:n=t.scriptable,_indexable:i=t.indexable,_allKeys:s=t.allKeys}=e;return{allKeys:s,scriptable:n,indexable:i,isScriptable:zn(n)?n:()=>n,isIndexable:zn(i)?i:()=>i}}const ym=(e,t)=>e?e+Na(t):t,Ua=(e,t)=>ot(t)&&e!=="adapters"&&(Object.getPrototypeOf(t)===null||t.constructor===Object);function ld(e,t,n){if(Object.prototype.hasOwnProperty.call(e,t)||t==="constructor")return e[t];const i=n();return e[t]=i,i}function wm(e,t,n){const{_proxy:i,_context:s,_subProxy:r,_descriptors:o}=e;let a=i[t];return zn(a)&&o.isScriptable(t)&&(a=km(t,a,e,n)),yt(a)&&a.length&&(a=Sm(t,a,e,o.isIndexable)),Ua(t,a)&&(a=Wi(a,s,r&&r[t],o)),a}function km(e,t,n,i){const{_proxy:s,_context:r,_subProxy:o,_stack:a}=n;if(a.has(e))throw new Error("Recursion detected: "+Array.from(a).join("->")+"->"+e);a.add(e);let l=t(r,o||i);return a.delete(e),Ua(e,l)&&(l=Xa(s._scopes,s,e,l)),l}function Sm(e,t,n,i){const{_proxy:s,_context:r,_subProxy:o,_descriptors:a}=n;if(typeof r.index<"u"&&i(e))return t[r.index%t.length];if(ot(t[0])){const l=t,c=s._scopes.filter(u=>u!==l);t=[];for(const u of l){const d=Xa(c,s,e,u);t.push(Wi(d,r,o&&o[e],a))}}return t}function cd(e,t,n){return zn(e)?e(t,n):e}const Mm=(e,t)=>e===!0?t:typeof e=="string"?Fn(t,e):void 0;function Pm(e,t,n,i,s){for(const r of t){const o=Mm(n,r);if(o){e.add(o);const a=cd(o._fallback,n,s);if(typeof a<"u"&&a!==n&&a!==i)return a}else if(o===!1&&typeof i<"u"&&n!==i)return null}return!1}function Xa(e,t,n,i){const s=t._rootScopes,r=cd(t._fallback,n,i),o=[...e,...s],a=new Set;a.add(i);let l=El(a,o,n,r||n,i);return l===null||typeof r<"u"&&r!==n&&(l=El(a,o,r,l,i),l===null)?!1:Ya(Array.from(a),[""],s,r,()=>Cm(t,n,i))}function El(e,t,n,i,s){for(;n;)n=Pm(e,t,n,i,s);return n}function Cm(e,t,n){const i=e._getTarget();t in i||(i[t]={});const s=i[t];return yt(s)&&ot(n)?n:s||{}}function Am(e,t,n,i){let s;for(const r of t)if(s=ud(ym(r,e),n),typeof s<"u")return Ua(e,s)?Xa(n,i,e,s):s}function ud(e,t){for(const n of t){if(!n)continue;const i=n[e];if(typeof i<"u")return i}}function Ol(e){let t=e._keys;return t||(t=e._keys=Tm(e._scopes)),t}function Tm(e){const t=new Set;for(const n of e)for(const i of Object.keys(n).filter(s=>!s.startsWith("_")))t.add(i);return Array.from(t)}function dd(e,t,n,i){const{iScale:s}=e,{key:r="r"}=this._parsing,o=new Array(i);let a,l,c,u;for(a=0,l=i;ate==="x"?"y":"x";function Em(e,t,n,i){const s=e.skip?t:e,r=t,o=n.skip?t:n,a=na(r,s),l=na(o,r);let c=a/(a+l),u=l/(a+l);c=isNaN(c)?0:c,u=isNaN(u)?0:u;const d=i*c,h=i*u;return{previous:{x:r.x-d*(o.x-s.x),y:r.y-d*(o.y-s.y)},next:{x:r.x+h*(o.x-s.x),y:r.y+h*(o.y-s.y)}}}function Om(e,t,n){const i=e.length;let s,r,o,a,l,c=Hi(e,0);for(let u=0;u!c.skip)),t.cubicInterpolationMode==="monotone")Rm(e,s);else{let c=i?e[e.length-1]:e[0];for(r=0,o=e.length;re.ownerDocument.defaultView.getComputedStyle(e,null);function zm(e,t){return so(e).getPropertyValue(t)}const jm=["top","right","bottom","left"];function ai(e,t,n){const i={};n=n?"-"+n:"";for(let s=0;s<4;s++){const r=jm[s];i[r]=parseFloat(e[t+"-"+r+n])||0}return i.width=i.left+i.right,i.height=i.top+i.bottom,i}const Nm=(e,t,n)=>(e>0||t>0)&&(!n||!n.shadowRoot);function Bm(e,t){const n=e.touches,i=n&&n.length?n[0]:e,{offsetX:s,offsetY:r}=i;let o=!1,a,l;if(Nm(s,r,e.target))a=s,l=r;else{const c=t.getBoundingClientRect();a=i.clientX-c.left,l=i.clientY-c.top,o=!0}return{x:a,y:l,box:o}}function qn(e,t){if("native"in e)return e;const{canvas:n,currentDevicePixelRatio:i}=t,s=so(n),r=s.boxSizing==="border-box",o=ai(s,"padding"),a=ai(s,"border","width"),{x:l,y:c,box:u}=Bm(e,n),d=o.left+(u&&a.left),h=o.top+(u&&a.top);let{width:f,height:g}=t;return r&&(f-=o.width+a.width,g-=o.height+a.height),{x:Math.round((l-d)/f*n.width/i),y:Math.round((c-h)/g*n.height/i)}}function Vm(e,t,n){let i,s;if(t===void 0||n===void 0){const r=e&&Ga(e);if(!r)t=e.clientWidth,n=e.clientHeight;else{const o=r.getBoundingClientRect(),a=so(r),l=ai(a,"border","width"),c=ai(a,"padding");t=o.width-c.width-l.width,n=o.height-c.height-l.height,i=Nr(a.maxWidth,r,"clientWidth"),s=Nr(a.maxHeight,r,"clientHeight")}}return{width:t,height:n,maxWidth:i||zr,maxHeight:s||zr}}const Mn=e=>Math.round(e*10)/10;function Wm(e,t,n,i){const s=so(e),r=ai(s,"margin"),o=Nr(s.maxWidth,e,"clientWidth")||zr,a=Nr(s.maxHeight,e,"clientHeight")||zr,l=Vm(e,t,n);let{width:c,height:u}=l;if(s.boxSizing==="content-box"){const h=ai(s,"border","width"),f=ai(s,"padding");c-=f.width+h.width,u-=f.height+h.height}return c=Math.max(0,c-r.width),u=Math.max(0,i?c/i:u-r.height),c=Mn(Math.min(c,o,l.maxWidth)),u=Mn(Math.min(u,a,l.maxHeight)),c&&!u&&(u=Mn(c/2)),(t!==void 0||n!==void 0)&&i&&l.height&&u>l.height&&(u=l.height,c=Mn(Math.floor(u*i))),{width:c,height:u}}function Ll(e,t,n){const i=t||1,s=Mn(e.height*i),r=Mn(e.width*i);e.height=Mn(e.height),e.width=Mn(e.width);const o=e.canvas;return o.style&&(n||!o.style.height&&!o.style.width)&&(o.style.height=`${e.height}px`,o.style.width=`${e.width}px`),e.currentDevicePixelRatio!==i||o.height!==s||o.width!==r?(e.currentDevicePixelRatio=i,o.height=s,o.width=r,e.ctx.setTransform(i,0,0,i,0,0),!0):!1}const Hm=(function(){let e=!1;try{const t={get passive(){return e=!0,!1}};Ka()&&(window.addEventListener("test",null,t),window.removeEventListener("test",null,t))}catch{}return e})();function Rl(e,t){const n=zm(e,t),i=n&&n.match(/^(\d+)(\.\d+)?px$/);return i?+i[1]:void 0}function Yn(e,t,n,i){return{x:e.x+n*(t.x-e.x),y:e.y+n*(t.y-e.y)}}function qm(e,t,n,i){return{x:e.x+n*(t.x-e.x),y:i==="middle"?n<.5?e.y:t.y:i==="after"?n<1?e.y:t.y:n>0?t.y:e.y}}function Ym(e,t,n,i){const s={x:e.cp2x,y:e.cp2y},r={x:t.cp1x,y:t.cp1y},o=Yn(e,s,n),a=Yn(s,r,n),l=Yn(r,t,n),c=Yn(o,a,n),u=Yn(a,l,n);return Yn(c,u,n)}const Um=function(e,t){return{x(n){return e+e+t-n},setWidth(n){t=n},textAlign(n){return n==="center"?n:n==="right"?"left":"right"},xPlus(n,i){return n-i},leftForLtr(n,i){return n-i}}},Xm=function(){return{x(e){return e},setWidth(e){},textAlign(e){return e},xPlus(e,t){return e+t},leftForLtr(e,t){return e}}};function Pi(e,t,n){return e?Um(t,n):Xm()}function fd(e,t){let n,i;(t==="ltr"||t==="rtl")&&(n=e.canvas.style,i=[n.getPropertyValue("direction"),n.getPropertyPriority("direction")],n.setProperty("direction",t,"important"),e.prevTextDirection=i)}function gd(e,t){t!==void 0&&(delete e.prevTextDirection,e.canvas.style.setProperty("direction",t[0],t[1]))}function pd(e){return e==="angle"?{between:Ps,compare:Kv,normalize:Xt}:{between:rn,compare:(t,n)=>t-n,normalize:t=>t}}function Il({start:e,end:t,count:n,loop:i,style:s}){return{start:e%n,end:t%n,loop:i&&(t-e+1)%n===0,style:s}}function Km(e,t,n){const{property:i,start:s,end:r}=n,{between:o,normalize:a}=pd(i),l=t.length;let{start:c,end:u,loop:d}=e,h,f;if(d){for(c+=l,u+=l,h=0,f=l;hl(s,x,_)&&a(s,x)!==0,S=()=>a(r,_)===0||l(r,x,_),D=()=>v||y(),T=()=>!v||S();for(let A=u,E=u;A<=d;++A)b=t[A%o],!b.skip&&(_=c(b[i]),_!==x&&(v=l(_,s,r),p===null&&D()&&(p=a(_,s)===0?A:E),p!==null&&T()&&(g.push(Il({start:p,end:A,loop:h,count:o,style:f})),p=null),E=A,x=_));return p!==null&&g.push(Il({start:p,end:d,loop:h,count:o,style:f})),g}function md(e,t){const n=[],i=e.segments;for(let s=0;ss&&e[r%t].skip;)r--;return r%=t,{start:s,end:r}}function Zm(e,t,n,i){const s=e.length,r=[];let o=t,a=e[t],l;for(l=t+1;l<=n;++l){const c=e[l%s];c.skip||c.stop?a.skip||(i=!1,r.push({start:t%s,end:(l-1)%s,loop:i}),t=o=c.stop?l:null):(o=l,a.skip&&(t=l)),a=c}return o!==null&&r.push({start:t%s,end:o%s,loop:i}),r}function Jm(e,t){const n=e.points,i=e.options.spanGaps,s=n.length;if(!s)return[];const r=!!e._loop,{start:o,end:a}=Gm(n,s,r,i);if(i===!0)return Fl(e,[{start:o,end:a,loop:r}],n,t);const l=aa({chart:t,initial:n.initial,numSteps:o,currentStep:Math.min(i-n.start,o)}))}_refresh(){this._request||(this._running=!0,this._request=td.call(window,()=>{this._update(),this._request=null,this._running&&this._refresh()}))}_update(t=Date.now()){let n=0;this._charts.forEach((i,s)=>{if(!i.running||!i.items.length)return;const r=i.items;let o=r.length-1,a=!1,l;for(;o>=0;--o)l=r[o],l._active?(l._total>i.duration&&(i.duration=l._total),l.tick(t),a=!0):(r[o]=r[r.length-1],r.pop());a&&(s.draw(),this._notify(s,i,t,"progress")),r.length||(i.running=!1,this._notify(s,i,t,"complete"),i.initial=!1),n+=r.length}),this._lastDate=t,n===0&&(this._running=!1)}_getAnims(t){const n=this._charts;let i=n.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},n.set(t,i)),i}listen(t,n,i){this._getAnims(t).listeners[n].push(i)}add(t,n){!n||!n.length||this._getAnims(t).items.push(...n)}has(t){return this._getAnims(t).items.length>0}start(t){const n=this._charts.get(t);n&&(n.running=!0,n.start=Date.now(),n.duration=n.items.reduce((i,s)=>Math.max(i,s._duration),0),this._refresh())}running(t){if(!this._running)return!1;const n=this._charts.get(t);return!(!n||!n.running||!n.items.length)}stop(t){const n=this._charts.get(t);if(!n||!n.items.length)return;const i=n.items;let s=i.length-1;for(;s>=0;--s)i[s].cancel();n.items=[],this._notify(t,n,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}var Ze=new e_;const jl="transparent",n_={boolean(e,t,n){return n>.5?t:e},color(e,t,n){const i=Al(e||jl),s=i.valid&&Al(t||jl);return s&&s.valid?s.mix(i,n).hexString():t},number(e,t,n){return e+(t-e)*n}};class i_{constructor(t,n,i,s){const r=n[i];s=xt([t.to,s,r,t.from]);const o=xt([t.from,r,s]);this._active=!0,this._fn=t.fn||n_[t.type||typeof o],this._easing=ps[t.easing]||ps.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=n,this._prop=i,this._from=o,this._to=s,this._promises=void 0}active(){return this._active}update(t,n,i){if(this._active){this._notify(!1);const s=this._target[this._prop],r=i-this._start,o=this._duration-r;this._start=i,this._duration=Math.floor(Math.max(o,t.duration)),this._total+=r,this._loop=!!t.loop,this._to=xt([t.to,n,s,t.from]),this._from=xt([t.from,s,n])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){const n=t-this._start,i=this._duration,s=this._prop,r=this._from,o=this._loop,a=this._to;let l;if(this._active=r!==a&&(o||n1?2-l:l,l=this._easing(Math.min(1,Math.max(0,l))),this._target[s]=this._fn(r,a,l)}wait(){const t=this._promises||(this._promises=[]);return new Promise((n,i)=>{t.push({res:n,rej:i})})}_notify(t){const n=t?"res":"rej",i=this._promises||[];for(let s=0;s{const r=t[s];if(!ot(r))return;const o={};for(const a of n)o[a]=r[a];(yt(r.properties)&&r.properties||[s]).forEach(a=>{(a===s||!i.has(a))&&i.set(a,o)})})}_animateOptions(t,n){const i=n.options,s=r_(t,i);if(!s)return[];const r=this._createAnimations(s,i);return i.$shared&&s_(t.options.$animations,i).then(()=>{t.options=i},()=>{}),r}_createAnimations(t,n){const i=this._properties,s=[],r=t.$animations||(t.$animations={}),o=Object.keys(n),a=Date.now();let l;for(l=o.length-1;l>=0;--l){const c=o[l];if(c.charAt(0)==="$")continue;if(c==="options"){s.push(...this._animateOptions(t,n));continue}const u=n[c];let d=r[c];const h=i.get(c);if(d)if(h&&d.active()){d.update(h,u,a);continue}else d.cancel();if(!h||!h.duration){t[c]=u;continue}r[c]=d=new i_(h,t,c,u),s.push(d)}return s}update(t,n){if(this._properties.size===0){Object.assign(t,n);return}const i=this._createAnimations(t,n);if(i.length)return Ze.add(this._chart,i),!0}}function s_(e,t){const n=[],i=Object.keys(t);for(let s=0;s0||!n&&r<0)return s.index}return null}function Wl(e,t){const{chart:n,_cachedMeta:i}=e,s=n._stacks||(n._stacks={}),{iScale:r,vScale:o,index:a}=i,l=r.axis,c=o.axis,u=c_(r,o,i),d=t.length;let h;for(let f=0;fn[i].axis===t).shift()}function h_(e,t){return jn(e,{active:!1,dataset:void 0,datasetIndex:t,index:t,mode:"default",type:"dataset"})}function f_(e,t,n){return jn(e,{active:!1,dataIndex:t,parsed:void 0,raw:void 0,element:n,index:t,mode:"default",type:"data"})}function Ji(e,t){const n=e.controller.index,i=e.vScale&&e.vScale.axis;if(i){t=t||e._parsed;for(const s of t){const r=s._stacks;if(!r||r[i]===void 0||r[i][n]===void 0)return;delete r[i][n],r[i]._visualValues!==void 0&&r[i]._visualValues[n]!==void 0&&delete r[i]._visualValues[n]}}}const Ao=e=>e==="reset"||e==="none",Hl=(e,t)=>t?e:Object.assign({},e),g_=(e,t,n)=>e&&!t.hidden&&t._stacked&&{keys:xd(n,!0),values:null};class Le{constructor(t,n){this.chart=t,this._ctx=t.ctx,this.index=n,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=Po(t.vScale,t),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(t){this.index!==t&&Ji(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,n=this._cachedMeta,i=this.getDataset(),s=(d,h,f,g)=>d==="x"?h:d==="r"?g:f,r=n.xAxisID=Q(i.xAxisID,Co(t,"x")),o=n.yAxisID=Q(i.yAxisID,Co(t,"y")),a=n.rAxisID=Q(i.rAxisID,Co(t,"r")),l=n.indexAxis,c=n.iAxisID=s(l,r,o,a),u=n.vAxisID=s(l,o,r,a);n.xScale=this.getScaleForId(r),n.yScale=this.getScaleForId(o),n.rScale=this.getScaleForId(a),n.iScale=this.getScaleForId(c),n.vScale=this.getScaleForId(u)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const n=this._cachedMeta;return t===n.iScale?n.vScale:n.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&Ml(this._data,this),t._stacked&&Ji(t)}_dataCheck(){const t=this.getDataset(),n=t.data||(t.data=[]),i=this._data;if(ot(n)){const s=this._cachedMeta;this._data=l_(n,s)}else if(i!==n){if(i){Ml(i,this);const s=this._cachedMeta;Ji(s),s._parsed=[]}n&&Object.isExtensible(n)&&Qv(n,this),this._syncList=[],this._data=n}}addElements(){const t=this._cachedMeta;this._dataCheck(),this.datasetElementType&&(t.dataset=new this.datasetElementType)}buildOrUpdateElements(t){const n=this._cachedMeta,i=this.getDataset();let s=!1;this._dataCheck();const r=n._stacked;n._stacked=Po(n.vScale,n),n.stack!==i.stack&&(s=!0,Ji(n),n.stack=i.stack),this._resyncElements(t),(s||r!==n._stacked)&&(Wl(this,n._parsed),n._stacked=Po(n.vScale,n))}configure(){const t=this.chart.config,n=t.datasetScopeKeys(this._type),i=t.getOptionScopes(this.getDataset(),n,!0);this.options=t.createResolver(i,this.getContext()),this._parsing=this.options.parsing,this._cachedDataOpts={}}parse(t,n){const{_cachedMeta:i,_data:s}=this,{iScale:r,_stacked:o}=i,a=r.axis;let l=t===0&&n===s.length?!0:i._sorted,c=t>0&&i._parsed[t-1],u,d,h;if(this._parsing===!1)i._parsed=s,i._sorted=!0,h=s;else{yt(s[t])?h=this.parseArrayData(i,s,t,n):ot(s[t])?h=this.parseObjectData(i,s,t,n):h=this.parsePrimitiveData(i,s,t,n);const f=()=>d[a]===null||c&&d[a]v||d=0;--h)if(!g()){this.updateRangeFromParsed(c,t,f,l);break}}return c}getAllParsedValues(t){const n=this._cachedMeta._parsed,i=[];let s,r,o;for(s=0,r=n.length;s=0&&tthis.getContext(i,s,n),v=c.resolveNamedOptions(h,f,g,d);return v.$shared&&(v.$shared=l,r[o]=Object.freeze(Hl(v,l))),v}_resolveAnimations(t,n,i){const s=this.chart,r=this._cachedDataOpts,o=`animation-${n}`,a=r[o];if(a)return a;let l;if(s.options.animation!==!1){const u=this.chart.config,d=u.datasetAnimationScopeKeys(this._type,n),h=u.getOptionScopes(this.getDataset(),d);l=u.createResolver(h,this.getContext(t,i,n))}const c=new bd(s,l&&l.animations);return l&&l._cacheable&&(r[o]=Object.freeze(c)),c}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,n){return!n||Ao(t)||this.chart._animationsDisabled}_getSharedOptions(t,n){const i=this.resolveDataElementOptions(t,n),s=this._sharedOptions,r=this.getSharedOptions(i),o=this.includeOptions(n,r)||r!==s;return this.updateSharedOptions(r,n,i),{sharedOptions:r,includeOptions:o}}updateElement(t,n,i,s){Ao(s)?Object.assign(t,i):this._resolveAnimations(n,s).update(t,i)}updateSharedOptions(t,n,i){t&&!Ao(n)&&this._resolveAnimations(void 0,n).update(t,i)}_setStyle(t,n,i,s){t.active=s;const r=this.getStyle(n,s);this._resolveAnimations(n,i,s).update(t,{options:!s&&this.getSharedOptions(r)||r})}removeHoverStyle(t,n,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,n,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const n=this._data,i=this._cachedMeta.data;for(const[a,l,c]of this._syncList)this[a](l,c);this._syncList=[];const s=i.length,r=n.length,o=Math.min(r,s);o&&this.parse(0,o),r>s?this._insertElements(s,r-s,t):r{for(c.length+=n,a=c.length-1;a>=o;a--)c[a]=c[a-n]};for(l(r),a=t;as-r))}return e._cache.$bar}function v_(e){const t=e.iScale,n=p_(t,e.type);let i=t._length,s,r,o,a;const l=()=>{o===32767||o===-32768||(Ms(a)&&(i=Math.min(i,Math.abs(o-a)||i)),a=o)};for(s=0,r=n.length;s0?s[e-1]:null,a=eMath.abs(a)&&(l=a,c=o),t[n.axis]=c,t._custom={barStart:l,barEnd:c,start:s,end:r,min:o,max:a}}function yd(e,t,n,i){return yt(e)?b_(e,t,n,i):t[n.axis]=n.parse(e,i),t}function ql(e,t,n,i){const s=e.iScale,r=e.vScale,o=s.getLabels(),a=s===r,l=[];let c,u,d,h;for(c=n,u=n+i;c=n?1:-1)}function y_(e){let t,n,i,s,r;return e.horizontal?(t=e.base>e.x,n="left",i="right"):(t=e.baseu.controller.options.grouped),r=i.options.stacked,o=[],a=this._cachedMeta.controller.getParsed(n),l=a&&a[i.axis],c=u=>{const d=u._parsed.find(f=>f[i.axis]===l),h=d&&d[u.vScale.axis];if(it(h)||isNaN(h))return!0};for(const u of s)if(!(n!==void 0&&c(u))&&((r===!1||o.indexOf(u.stack)===-1||r===void 0&&u.stack===void 0)&&o.push(u.stack),u.index===t))break;return o.length||o.push(void 0),o}_getStackCount(t){return this._getStacks(void 0,t).length}_getAxisCount(){return this._getAxis().length}getFirstScaleIdForIndexAxis(){const t=this.chart.scales,n=this.chart.options.indexAxis;return Object.keys(t).filter(i=>t[i].axis===n).shift()}_getAxis(){const t={},n=this.getFirstScaleIdForIndexAxis();for(const i of this.chart.data.datasets)t[Q(this.chart.options.indexAxis==="x"?i.xAxisID:i.yAxisID,n)]=!0;return Object.keys(t)}_getStackIndex(t,n,i){const s=this._getStacks(t,i),r=n!==void 0?s.indexOf(n):-1;return r===-1?s.length-1:r}_getRuler(){const t=this.options,n=this._cachedMeta,i=n.iScale,s=[];let r,o;for(r=0,o=n.data.length;r=0;--i)n=Math.max(n,t[i].size(this.resolveDataElementOptions(i))/2);return n>0&&n}getLabelAndValue(t){const n=this._cachedMeta,i=this.chart.data.labels||[],{xScale:s,yScale:r}=n,o=this.getParsed(t),a=s.getLabelForValue(o.x),l=r.getLabelForValue(o.y),c=o._custom;return{label:i[t]||"",value:"("+a+", "+l+(c?", "+c:"")+")"}}update(t){const n=this._cachedMeta.data;this.updateElements(n,0,n.length,t)}updateElements(t,n,i,s){const r=s==="reset",{iScale:o,vScale:a}=this._cachedMeta,{sharedOptions:l,includeOptions:c}=this._getSharedOptions(n,s),u=o.axis,d=a.axis;for(let h=n;hPs(x,a,l,!0)?1:Math.max(y,y*n,S,S*n),g=(x,y,S)=>Ps(x,a,l,!0)?-1:Math.min(y,y*n,S,S*n),v=f(0,c,d),p=f(Rt,u,h),_=g(ut,c,d),b=g(ut+Rt,u,h);i=(v-_)/2,s=(p-b)/2,r=-(v+_)/2,o=-(p+b)/2}return{ratioX:i,ratioY:s,offsetX:r,offsetY:o}}class Gn extends Le{constructor(t,n){super(t,n),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,n){const i=this.getDataset().data,s=this._cachedMeta;if(this._parsing===!1)s._parsed=i;else{let r=l=>+i[l];if(ot(i[t])){const{key:l="value"}=this._parsing;r=c=>+Fn(i[c],l)}let o,a;for(o=t,a=t+n;o0&&!isNaN(t)?wt*(Math.abs(t)/n):0}getLabelAndValue(t){const n=this._cachedMeta,i=this.chart,s=i.data.labels||[],r=Ys(n._parsed[t],i.options.locale);return{label:s[t]||"",value:r}}getMaxBorderWidth(t){let n=0;const i=this.chart;let s,r,o,a,l;if(!t){for(s=0,r=i.data.datasets.length;st!=="spacing",_indexable:t=>t!=="spacing"&&!t.startsWith("borderDash")&&!t.startsWith("hoverBorderDash")}),z(Gn,"overrides",{aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const n=t.data,{labels:{pointStyle:i,textAlign:s,color:r,useBorderRadius:o,borderRadius:a}}=t.legend.options;return n.labels.length&&n.datasets.length?n.labels.map((l,c)=>{const d=t.getDatasetMeta(0).controller.getStyle(c);return{text:l,fillStyle:d.backgroundColor,fontColor:r,hidden:!t.getDataVisibility(c),lineDash:d.borderDash,lineDashOffset:d.borderDashOffset,lineJoin:d.borderJoinStyle,lineWidth:d.borderWidth,strokeStyle:d.borderColor,textAlign:s,pointStyle:i,borderRadius:o&&(a||d.borderRadius),index:c}}):[]}},onClick(t,n,i){i.chart.toggleDataVisibility(n.index),i.chart.update()}}}});class Sr extends Le{initialize(){this.enableOptionSharing=!0,this.supportsDecimation=!0,super.initialize()}update(t){const n=this._cachedMeta,{dataset:i,data:s=[],_dataset:r}=n,o=this.chart._animationsDisabled;let{start:a,count:l}=nd(n,s,o);this._drawStart=a,this._drawCount=l,id(n)&&(a=0,l=s.length),i._chart=this.chart,i._datasetIndex=this.index,i._decimated=!!r._decimated,i.points=s;const c=this.resolveDatasetElementOptions(t);this.options.showLine||(c.borderWidth=0),c.segment=this.options.segment,this.updateElement(i,void 0,{animated:!o,options:c},t),this.updateElements(s,a,l,t)}updateElements(t,n,i,s){const r=s==="reset",{iScale:o,vScale:a,_stacked:l,_dataset:c}=this._cachedMeta,{sharedOptions:u,includeOptions:d}=this._getSharedOptions(n,s),h=o.axis,f=a.axis,{spanGaps:g,segment:v}=this.options,p=Vi(g)?g:Number.POSITIVE_INFINITY,_=this.chart._animationsDisabled||r||s==="none",b=n+i,x=t.length;let y=n>0&&this.getParsed(n-1);for(let S=0;S=b){T.skip=!0;continue}const A=this.getParsed(S),E=it(A[f]),R=T[h]=o.getPixelForValue(A[h],S),I=T[f]=r||E?a.getBasePixel():a.getPixelForValue(l?this.applyStack(a,A,l):A[f],S);T.skip=isNaN(R)||isNaN(I)||E,T.stop=S>0&&Math.abs(A[h]-y[h])>p,v&&(T.parsed=A,T.raw=c.data[S]),d&&(T.options=u||this.resolveDataElementOptions(S,D.active?"active":s)),_||this.updateElement(D,S,T,s),y=A}}getMaxOverflow(){const t=this._cachedMeta,n=t.dataset,i=n.options&&n.options.borderWidth||0,s=t.data||[];if(!s.length)return i;const r=s[0].size(this.resolveDataElementOptions(0)),o=s[s.length-1].size(this.resolveDataElementOptions(s.length-1));return Math.max(i,r,o)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}}z(Sr,"id","line"),z(Sr,"defaults",{datasetElementType:"line",dataElementType:"point",showLine:!0,spanGaps:!1}),z(Sr,"overrides",{scales:{_index_:{type:"category"},_value_:{type:"linear"}}});class ms extends Le{constructor(t,n){super(t,n),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const n=this._cachedMeta,i=this.chart,s=i.data.labels||[],r=Ys(n._parsed[t].r,i.options.locale);return{label:s[t]||"",value:r}}parseObjectData(t,n,i,s){return dd.bind(this)(t,n,i,s)}update(t){const n=this._cachedMeta.data;this._updateRadius(),this.updateElements(n,0,n.length,t)}getMinMax(){const t=this._cachedMeta,n={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY};return t.data.forEach((i,s)=>{const r=this.getParsed(s).r;!isNaN(r)&&this.chart.getDataVisibility(s)&&(rn.max&&(n.max=r))}),n}_updateRadius(){const t=this.chart,n=t.chartArea,i=t.options,s=Math.min(n.right-n.left,n.bottom-n.top),r=Math.max(s/2,0),o=Math.max(i.cutoutPercentage?r/100*i.cutoutPercentage:1,0),a=(r-o)/t.getVisibleDatasetCount();this.outerRadius=r-a*this.index,this.innerRadius=this.outerRadius-a}updateElements(t,n,i,s){const r=s==="reset",o=this.chart,l=o.options.animation,c=this._cachedMeta.rScale,u=c.xCenter,d=c.yCenter,h=c.getIndexAngle(0)-.5*ut;let f=h,g;const v=360/this.countVisibleElements();for(g=0;g{!isNaN(this.getParsed(s).r)&&this.chart.getDataVisibility(s)&&n++}),n}_computeAngle(t,n,i){return this.chart.getDataVisibility(t)?De(this.resolveDataElementOptions(t,n).angle||i):0}}z(ms,"id","polarArea"),z(ms,"defaults",{dataElementType:"arc",animation:{animateRotate:!0,animateScale:!0},animations:{numbers:{type:"number",properties:["x","y","startAngle","endAngle","innerRadius","outerRadius"]}},indexAxis:"r",startAngle:0}),z(ms,"overrides",{aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const n=t.data;if(n.labels.length&&n.datasets.length){const{labels:{pointStyle:i,color:s}}=t.legend.options;return n.labels.map((r,o)=>{const l=t.getDatasetMeta(0).controller.getStyle(o);return{text:r,fillStyle:l.backgroundColor,strokeStyle:l.borderColor,fontColor:s,lineWidth:l.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(o),index:o}})}return[]}},onClick(t,n,i){i.chart.toggleDataVisibility(n.index),i.chart.update()}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}});class ra extends Gn{}z(ra,"id","pie"),z(ra,"defaults",{cutout:0,rotation:0,circumference:360,radius:"100%"});class Mr extends Le{getLabelAndValue(t){const n=this._cachedMeta.vScale,i=this.getParsed(t);return{label:n.getLabels()[t],value:""+n.getLabelForValue(i[n.axis])}}parseObjectData(t,n,i,s){return dd.bind(this)(t,n,i,s)}update(t){const n=this._cachedMeta,i=n.dataset,s=n.data||[],r=n.iScale.getLabels();if(i.points=s,t!=="resize"){const o=this.resolveDatasetElementOptions(t);this.options.showLine||(o.borderWidth=0);const a={_loop:!0,_fullLoop:r.length===s.length,options:o};this.updateElement(i,void 0,a,t)}this.updateElements(s,0,s.length,t)}updateElements(t,n,i,s){const r=this._cachedMeta.rScale,o=s==="reset";for(let a=n;a0&&this.getParsed(n-1);for(let y=n;y0&&Math.abs(D[f]-x[f])>_,p&&(T.parsed=D,T.raw=c.data[y]),h&&(T.options=d||this.resolveDataElementOptions(y,S.active?"active":s)),b||this.updateElement(S,y,T,s),x=D}this.updateSharedOptions(d,s,u)}getMaxOverflow(){const t=this._cachedMeta,n=t.data||[];if(!this.options.showLine){let a=0;for(let l=n.length-1;l>=0;--l)a=Math.max(a,n[l].size(this.resolveDataElementOptions(l))/2);return a>0&&a}const i=t.dataset,s=i.options&&i.options.borderWidth||0;if(!n.length)return s;const r=n[0].size(this.resolveDataElementOptions(0)),o=n[n.length-1].size(this.resolveDataElementOptions(n.length-1));return Math.max(s,r,o)/2}}z(Pr,"id","scatter"),z(Pr,"defaults",{datasetElementType:!1,dataElementType:"point",showLine:!1,fill:!1}),z(Pr,"overrides",{interaction:{mode:"point"},scales:{x:{type:"linear"},y:{type:"linear"}}});var P_=Object.freeze({__proto__:null,BarController:wr,BubbleController:kr,DoughnutController:Gn,LineController:Sr,PieController:ra,PolarAreaController:ms,RadarController:Mr,ScatterController:Pr});function Wn(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class Za{constructor(t){z(this,"options");this.options=t||{}}static override(t){Object.assign(Za.prototype,t)}init(){}formats(){return Wn()}parse(){return Wn()}format(){return Wn()}add(){return Wn()}diff(){return Wn()}startOf(){return Wn()}endOf(){return Wn()}}var C_={_date:Za};function A_(e,t,n,i){const{controller:s,data:r,_sorted:o}=e,a=s._cachedMeta.iScale,l=e.dataset&&e.dataset.options?e.dataset.options.spanGaps:null;if(a&&t===a.axis&&t!=="r"&&o&&r.length){const c=a._reversePixels?Zv:on;if(i){if(s._sharedOptions){const u=r[0],d=typeof u.getRange=="function"&&u.getRange(t);if(d){const h=c(r,t,n-d),f=c(r,t,n+d);return{lo:h.lo,hi:f.hi}}}}else{const u=c(r,t,n);if(l){const{vScale:d}=s._cachedMeta,{_parsed:h}=e,f=h.slice(0,u.lo+1).reverse().findIndex(v=>!it(v[d.axis]));u.lo-=Math.max(0,f);const g=h.slice(u.hi).findIndex(v=>!it(v[d.axis]));u.hi+=Math.max(0,g)}return u}}return{lo:0,hi:r.length-1}}function ro(e,t,n,i,s){const r=e.getSortedVisibleDatasetMetas(),o=n[t];for(let a=0,l=r.length;a{l[o]&&l[o](t[n],s)&&(r.push({element:l,datasetIndex:c,index:u}),a=a||l.inRange(t.x,t.y,s))}),i&&!a?[]:r}var O_={modes:{index(e,t,n,i){const s=qn(t,e),r=n.axis||"x",o=n.includeInvisible||!1,a=n.intersect?Do(e,s,r,i,o):Eo(e,s,r,!1,i,o),l=[];return a.length?(e.getSortedVisibleDatasetMetas().forEach(c=>{const u=a[0].index,d=c.data[u];d&&!d.skip&&l.push({element:d,datasetIndex:c.index,index:u})}),l):[]},dataset(e,t,n,i){const s=qn(t,e),r=n.axis||"xy",o=n.includeInvisible||!1;let a=n.intersect?Do(e,s,r,i,o):Eo(e,s,r,!1,i,o);if(a.length>0){const l=a[0].datasetIndex,c=e.getDatasetMeta(l).data;a=[];for(let u=0;un.pos===t)}function Kl(e,t){return e.filter(n=>wd.indexOf(n.pos)===-1&&n.box.axis===t)}function $i(e,t){return e.sort((n,i)=>{const s=t?i:n,r=t?n:i;return s.weight===r.weight?s.index-r.index:s.weight-r.weight})}function L_(e){const t=[];let n,i,s,r,o,a;for(n=0,i=(e||[]).length;nc.box.fullSize),!0),i=$i(Qi(t,"left"),!0),s=$i(Qi(t,"right")),r=$i(Qi(t,"top"),!0),o=$i(Qi(t,"bottom")),a=Kl(t,"x"),l=Kl(t,"y");return{fullSize:n,leftAndTop:i.concat(r),rightAndBottom:s.concat(l).concat(o).concat(a),chartArea:Qi(t,"chartArea"),vertical:i.concat(s).concat(l),horizontal:r.concat(o).concat(a)}}function Gl(e,t,n,i){return Math.max(e[n],t[n])+Math.max(e[i],t[i])}function kd(e,t){e.top=Math.max(e.top,t.top),e.left=Math.max(e.left,t.left),e.bottom=Math.max(e.bottom,t.bottom),e.right=Math.max(e.right,t.right)}function z_(e,t,n,i){const{pos:s,box:r}=n,o=e.maxPadding;if(!ot(s)){n.size&&(e[s]-=n.size);const d=i[n.stack]||{size:0,count:1};d.size=Math.max(d.size,n.horizontal?r.height:r.width),n.size=d.size/d.count,e[s]+=n.size}r.getPadding&&kd(o,r.getPadding());const a=Math.max(0,t.outerWidth-Gl(o,e,"left","right")),l=Math.max(0,t.outerHeight-Gl(o,e,"top","bottom")),c=a!==e.w,u=l!==e.h;return e.w=a,e.h=l,n.horizontal?{same:c,other:u}:{same:u,other:c}}function j_(e){const t=e.maxPadding;function n(i){const s=Math.max(t[i]-e[i],0);return e[i]+=s,s}e.y+=n("top"),e.x+=n("left"),n("right"),n("bottom")}function N_(e,t){const n=t.maxPadding;function i(s){const r={left:0,top:0,right:0,bottom:0};return s.forEach(o=>{r[o]=Math.max(t[o],n[o])}),r}return i(e?["left","right"]:["top","bottom"])}function as(e,t,n,i){const s=[];let r,o,a,l,c,u;for(r=0,o=e.length,c=0;r{typeof v.beforeLayout=="function"&&v.beforeLayout()});const u=l.reduce((v,p)=>p.box.options&&p.box.options.display===!1?v:v+1,0)||1,d=Object.freeze({outerWidth:t,outerHeight:n,padding:s,availableWidth:r,availableHeight:o,vBoxMaxWidth:r/2/u,hBoxMaxHeight:o/2}),h=Object.assign({},s);kd(h,Yt(i));const f=Object.assign({maxPadding:h,w:r,h:o,x:s.left,y:s.top},s),g=I_(l.concat(c),d);as(a.fullSize,f,d,g),as(l,f,d,g),as(c,f,d,g)&&as(l,f,d,g),j_(f),Zl(a.leftAndTop,f,d,g),f.x+=f.w,f.y+=f.h,Zl(a.rightAndBottom,f,d,g),e.chartArea={left:f.left,top:f.top,right:f.left+f.w,bottom:f.top+f.h,height:f.h,width:f.w},gt(a.chartArea,v=>{const p=v.box;Object.assign(p,e.chartArea),p.update(f.w,f.h,{left:0,top:0,right:0,bottom:0})})}};class Sd{acquireContext(t,n){}releaseContext(t){return!1}addEventListener(t,n,i){}removeEventListener(t,n,i){}getDevicePixelRatio(){return 1}getMaximumSize(t,n,i,s){return n=Math.max(0,n||t.width),i=i||t.height,{width:n,height:Math.max(0,s?Math.floor(n/s):i)}}isAttached(t){return!0}updateConfig(t){}}class B_ extends Sd{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}}const Cr="$chartjs",V_={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},Jl=e=>e===null||e==="";function W_(e,t){const n=e.style,i=e.getAttribute("height"),s=e.getAttribute("width");if(e[Cr]={initial:{height:i,width:s,style:{display:n.display,height:n.height,width:n.width}}},n.display=n.display||"block",n.boxSizing=n.boxSizing||"border-box",Jl(s)){const r=Rl(e,"width");r!==void 0&&(e.width=r)}if(Jl(i))if(e.style.height==="")e.height=e.width/(t||2);else{const r=Rl(e,"height");r!==void 0&&(e.height=r)}return e}const Md=Hm?{passive:!0}:!1;function H_(e,t,n){e&&e.addEventListener(t,n,Md)}function q_(e,t,n){e&&e.canvas&&e.canvas.removeEventListener(t,n,Md)}function Y_(e,t){const n=V_[e.type]||e.type,{x:i,y:s}=qn(e,t);return{type:n,chart:t,native:e,x:i!==void 0?i:null,y:s!==void 0?s:null}}function Br(e,t){for(const n of e)if(n===t||n.contains(t))return!0}function U_(e,t,n){const i=e.canvas,s=new MutationObserver(r=>{let o=!1;for(const a of r)o=o||Br(a.addedNodes,i),o=o&&!Br(a.removedNodes,i);o&&n()});return s.observe(document,{childList:!0,subtree:!0}),s}function X_(e,t,n){const i=e.canvas,s=new MutationObserver(r=>{let o=!1;for(const a of r)o=o||Br(a.removedNodes,i),o=o&&!Br(a.addedNodes,i);o&&n()});return s.observe(document,{childList:!0,subtree:!0}),s}const As=new Map;let Ql=0;function Pd(){const e=window.devicePixelRatio;e!==Ql&&(Ql=e,As.forEach((t,n)=>{n.currentDevicePixelRatio!==e&&t()}))}function K_(e,t){As.size||window.addEventListener("resize",Pd),As.set(e,t)}function G_(e){As.delete(e),As.size||window.removeEventListener("resize",Pd)}function Z_(e,t,n){const i=e.canvas,s=i&&Ga(i);if(!s)return;const r=ed((a,l)=>{const c=s.clientWidth;n(a,l),c{const l=a[0],c=l.contentRect.width,u=l.contentRect.height;c===0&&u===0||r(c,u)});return o.observe(s),K_(e,r),o}function Oo(e,t,n){n&&n.disconnect(),t==="resize"&&G_(e)}function J_(e,t,n){const i=e.canvas,s=ed(r=>{e.ctx!==null&&n(Y_(r,e))},e);return H_(i,t,s),s}class Q_ extends Sd{acquireContext(t,n){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(W_(t,n),i):null}releaseContext(t){const n=t.canvas;if(!n[Cr])return!1;const i=n[Cr].initial;["height","width"].forEach(r=>{const o=i[r];it(o)?n.removeAttribute(r):n.setAttribute(r,o)});const s=i.style||{};return Object.keys(s).forEach(r=>{n.style[r]=s[r]}),n.width=n.width,delete n[Cr],!0}addEventListener(t,n,i){this.removeEventListener(t,n);const s=t.$proxies||(t.$proxies={}),o={attach:U_,detach:X_,resize:Z_}[n]||J_;s[n]=o(t,n,i)}removeEventListener(t,n){const i=t.$proxies||(t.$proxies={}),s=i[n];if(!s)return;({attach:Oo,detach:Oo,resize:Oo}[n]||q_)(t,n,s),i[n]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,n,i,s){return Wm(t,n,i,s)}isAttached(t){const n=t&&Ga(t);return!!(n&&n.isConnected)}}function $_(e){return!Ka()||typeof OffscreenCanvas<"u"&&e instanceof OffscreenCanvas?B_:Q_}var fr;let hn=(fr=class{constructor(){z(this,"x");z(this,"y");z(this,"active",!1);z(this,"options");z(this,"$animations")}tooltipPosition(t){const{x:n,y:i}=this.getProps(["x","y"],t);return{x:n,y:i}}hasValue(){return Vi(this.x)&&Vi(this.y)}getProps(t,n){const i=this.$animations;if(!n||!i)return this;const s={};return t.forEach(r=>{s[r]=i[r]&&i[r].active()?i[r]._to:this[r]}),s}},z(fr,"defaults",{}),z(fr,"defaultRoutes"),fr);function tb(e,t){const n=e.options.ticks,i=eb(e),s=Math.min(n.maxTicksLimit||i,i),r=n.major.enabled?ib(t):[],o=r.length,a=r[0],l=r[o-1],c=[];if(o>s)return sb(t,c,r,o/s),c;const u=nb(r,t,s);if(o>0){let d,h;const f=o>1?Math.round((l-a)/(o-1)):null;for(ir(t,c,u,it(f)?0:a-f,a),d=0,h=o-1;ds)return l}return Math.max(s,1)}function ib(e){const t=[];let n,i;for(n=0,i=e.length;ne==="left"?"right":e==="right"?"left":e,$l=(e,t,n)=>t==="top"||t==="left"?e[t]+n:e[t]-n,tc=(e,t)=>Math.min(t||e,e);function ec(e,t){const n=[],i=e.length/t,s=e.length;let r=0;for(;ro+a)))return l}function lb(e,t){gt(e,n=>{const i=n.gc,s=i.length/2;let r;if(s>t){for(r=0;ri?i:n,i=s&&n>i?n:i,{min:le(n,le(i,n)),max:le(i,le(n,i))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}getLabelItems(t=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(t))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){pt(this.options.beforeUpdate,[this])}update(t,n,i){const{beginAtZero:s,grace:r,ticks:o}=this.options,a=o.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=n,this._margins=i=Object.assign({left:0,right:0,top:0,bottom:0},i),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+i.left+i.right:this.height+i.top+i.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=xm(this,r,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const l=a=r||i<=1||!this.isHorizontal()){this.labelRotation=s;return}const u=this._getLabelSizes(),d=u.widest.width,h=u.highest.height,f=Wt(this.chart.width-d,0,this.maxWidth);a=t.offset?this.maxWidth/i:f/(i-1),d+6>a&&(a=f/(i-(t.offset?.5:1)),l=this.maxHeight-ts(t.grid)-n.padding-nc(t.title,this.chart.options.font),c=Math.sqrt(d*d+h*h),o=Ba(Math.min(Math.asin(Wt((u.highest.height+6)/a,-1,1)),Math.asin(Wt(l/c,-1,1))-Math.asin(Wt(h/c,-1,1)))),o=Math.max(s,Math.min(r,o))),this.labelRotation=o}afterCalculateLabelRotation(){pt(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){pt(this.options.beforeFit,[this])}fit(){const t={width:0,height:0},{chart:n,options:{ticks:i,title:s,grid:r}}=this,o=this._isVisible(),a=this.isHorizontal();if(o){const l=nc(s,n.options.font);if(a?(t.width=this.maxWidth,t.height=ts(r)+l):(t.height=this.maxHeight,t.width=ts(r)+l),i.display&&this.ticks.length){const{first:c,last:u,widest:d,highest:h}=this._getLabelSizes(),f=i.padding*2,g=De(this.labelRotation),v=Math.cos(g),p=Math.sin(g);if(a){const _=i.mirror?0:p*d.width+v*h.height;t.height=Math.min(this.maxHeight,t.height+_+f)}else{const _=i.mirror?0:v*d.width+p*h.height;t.width=Math.min(this.maxWidth,t.width+_+f)}this._calculatePadding(c,u,p,v)}}this._handleMargins(),a?(this.width=this._length=n.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=n.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,n,i,s){const{ticks:{align:r,padding:o},position:a}=this.options,l=this.labelRotation!==0,c=a!=="top"&&this.axis==="x";if(this.isHorizontal()){const u=this.getPixelForTick(0)-this.left,d=this.right-this.getPixelForTick(this.ticks.length-1);let h=0,f=0;l?c?(h=s*t.width,f=i*n.height):(h=i*t.height,f=s*n.width):r==="start"?f=n.width:r==="end"?h=t.width:r!=="inner"&&(h=t.width/2,f=n.width/2),this.paddingLeft=Math.max((h-u+o)*this.width/(this.width-u),0),this.paddingRight=Math.max((f-d+o)*this.width/(this.width-d),0)}else{let u=n.height/2,d=t.height/2;r==="start"?(u=0,d=t.height):r==="end"&&(u=n.height,d=0),this.paddingTop=u+o,this.paddingBottom=d+o}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){pt(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:n}=this.options;return n==="top"||n==="bottom"||t==="x"}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){this.beforeTickToLabelConversion(),this.generateTickLabels(t);let n,i;for(n=0,i=t.length;n({width:o[E]||0,height:a[E]||0});return{first:A(0),last:A(n-1),widest:A(D),highest:A(T),widths:o,heights:a}}getLabelForValue(t){return t}getPixelForValue(t,n){return NaN}getValueForPixel(t){}getPixelForTick(t){const n=this.ticks;return t<0||t>n.length-1?null:this.getPixelForValue(n[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);const n=this._startPixel+t*this._length;return Gv(this._alignToPixels?Vn(this.chart,n,0):n)}getDecimalForPixel(t){const n=(t-this._startPixel)/this._length;return this._reversePixels?1-n:n}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:n}=this;return t<0&&n<0?n:t>0&&n>0?t:0}getContext(t){const n=this.ticks||[];if(t>=0&&ta*s?a/i:l/s:l*s0}_computeGridLineItems(t){const n=this.axis,i=this.chart,s=this.options,{grid:r,position:o,border:a}=s,l=r.offset,c=this.isHorizontal(),d=this.ticks.length+(l?1:0),h=ts(r),f=[],g=a.setContext(this.getContext()),v=g.display?g.width:0,p=v/2,_=function(X){return Vn(i,X,v)};let b,x,y,S,D,T,A,E,R,I,C,V;if(o==="top")b=_(this.bottom),T=this.bottom-h,E=b-p,I=_(t.top)+p,V=t.bottom;else if(o==="bottom")b=_(this.top),I=t.top,V=_(t.bottom)-p,T=b+p,E=this.top+h;else if(o==="left")b=_(this.right),D=this.right-h,A=b-p,R=_(t.left)+p,C=t.right;else if(o==="right")b=_(this.left),R=t.left,C=_(t.right)-p,D=b+p,A=this.left+h;else if(n==="x"){if(o==="center")b=_((t.top+t.bottom)/2+.5);else if(ot(o)){const X=Object.keys(o)[0],G=o[X];b=_(this.chart.scales[X].getPixelForValue(G))}I=t.top,V=t.bottom,T=b+p,E=T+h}else if(n==="y"){if(o==="center")b=_((t.left+t.right)/2);else if(ot(o)){const X=Object.keys(o)[0],G=o[X];b=_(this.chart.scales[X].getPixelForValue(G))}D=b-p,A=D-h,R=t.left,C=t.right}const q=Q(s.ticks.maxTicksLimit,d),N=Math.max(1,Math.ceil(d/q));for(x=0;x0&&(oe-=Ot/2);break}bt={left:oe,top:Ke,width:Ot+dt.width,height:vt+dt.height,color:N.backdropColor}}p.push({label:y,font:E,textOffset:C,options:{rotation:v,color:G,strokeColor:B,strokeWidth:U,textAlign:ct,textBaseline:V,translation:[S,D],backdrop:bt}})}return p}_getXAxisLabelAlignment(){const{position:t,ticks:n}=this.options;if(-De(this.labelRotation))return t==="top"?"left":"right";let s="center";return n.align==="start"?s="left":n.align==="end"?s="right":n.align==="inner"&&(s="inner"),s}_getYAxisLabelAlignment(t){const{position:n,ticks:{crossAlign:i,mirror:s,padding:r}}=this.options,o=this._getLabelSizes(),a=t+r,l=o.widest.width;let c,u;return n==="left"?s?(u=this.right+r,i==="near"?c="left":i==="center"?(c="center",u+=l/2):(c="right",u+=l)):(u=this.right-a,i==="near"?c="right":i==="center"?(c="center",u-=l/2):(c="left",u=this.left)):n==="right"?s?(u=this.left+r,i==="near"?c="right":i==="center"?(c="center",u-=l/2):(c="left",u-=l)):(u=this.left+a,i==="near"?c="left":i==="center"?(c="center",u+=l/2):(c="right",u=this.right)):c="right",{textAlign:c,x:u}}_computeLabelArea(){if(this.options.ticks.mirror)return;const t=this.chart,n=this.options.position;if(n==="left"||n==="right")return{top:0,left:this.left,bottom:t.height,right:this.right};if(n==="top"||n==="bottom")return{top:this.top,left:0,bottom:this.bottom,right:t.width}}drawBackground(){const{ctx:t,options:{backgroundColor:n},left:i,top:s,width:r,height:o}=this;n&&(t.save(),t.fillStyle=n,t.fillRect(i,s,r,o),t.restore())}getLineWidthForValue(t){const n=this.options.grid;if(!this._isVisible()||!n.display)return 0;const s=this.ticks.findIndex(r=>r.value===t);return s>=0?n.setContext(this.getContext(s)).lineWidth:0}drawGrid(t){const n=this.options.grid,i=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t));let r,o;const a=(l,c,u)=>{!u.width||!u.color||(i.save(),i.lineWidth=u.width,i.strokeStyle=u.color,i.setLineDash(u.borderDash||[]),i.lineDashOffset=u.borderDashOffset,i.beginPath(),i.moveTo(l.x,l.y),i.lineTo(c.x,c.y),i.stroke(),i.restore())};if(n.display)for(r=0,o=s.length;r{this.draw(r)}}]:[{z:i,draw:r=>{this.drawBackground(),this.drawGrid(r),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:n,draw:r=>{this.drawLabels(r)}}]}getMatchingVisibleMetas(t){const n=this.chart.getSortedVisibleDatasetMetas(),i=this.axis+"AxisID",s=[];let r,o;for(r=0,o=n.length;r{const i=n.split("."),s=i.pop(),r=[e].concat(i).join("."),o=t[n].split("."),a=o.pop(),l=o.join(".");kt.route(r,s,l,a)})}function pb(e){return"id"in e&&"defaults"in e}class vb{constructor(){this.controllers=new sr(Le,"datasets",!0),this.elements=new sr(hn,"elements"),this.plugins=new sr(Object,"plugins"),this.scales=new sr(pi,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,n,i){[...n].forEach(s=>{const r=i||this._getRegistryForType(s);i||r.isForType(s)||r===this.plugins&&s.id?this._exec(t,r,s):gt(s,o=>{const a=i||this._getRegistryForType(o);this._exec(t,a,o)})})}_exec(t,n,i){const s=Na(t);pt(i["before"+s],[],i),n[t](i),pt(i["after"+s],[],i)}_getRegistryForType(t){for(let n=0;nr.filter(a=>!o.some(l=>a.plugin.id===l.plugin.id));this._notify(s(n,i),t,"stop"),this._notify(s(i,n),t,"start")}}function _b(e){const t={},n=[],i=Object.keys(ze.plugins.items);for(let r=0;r1&&ic(e[0].toLowerCase());if(i)return i}throw new Error(`Cannot determine type of '${e}' axis. Please provide 'axis' or 'position' option.`)}function sc(e,t,n){if(n[t+"AxisID"]===e)return{axis:t}}function Mb(e,t){if(t.data&&t.data.datasets){const n=t.data.datasets.filter(i=>i.xAxisID===e||i.yAxisID===e);if(n.length)return sc(e,"x",n[0])||sc(e,"y",n[0])}return{}}function Pb(e,t){const n=hi[e.type]||{scales:{}},i=t.scales||{},s=oa(e.type,t),r=Object.create(null);return Object.keys(i).forEach(o=>{const a=i[o];if(!ot(a))return console.error(`Invalid scale configuration for scale: ${o}`);if(a._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${o}`);const l=aa(o,a,Mb(o,e),kt.scales[a.type]),c=kb(l,s),u=n.scales||{};r[o]=fs(Object.create(null),[{axis:l},a,u[l],u[c]])}),e.data.datasets.forEach(o=>{const a=o.type||e.type,l=o.indexAxis||oa(a,t),u=(hi[a]||{}).scales||{};Object.keys(u).forEach(d=>{const h=wb(d,l),f=o[h+"AxisID"]||h;r[f]=r[f]||Object.create(null),fs(r[f],[{axis:h},i[f],u[d]])})}),Object.keys(r).forEach(o=>{const a=r[o];fs(a,[kt.scales[a.type],kt.scale])}),r}function Cd(e){const t=e.options||(e.options={});t.plugins=Q(t.plugins,{}),t.scales=Pb(e,t)}function Ad(e){return e=e||{},e.datasets=e.datasets||[],e.labels=e.labels||[],e}function Cb(e){return e=e||{},e.data=Ad(e.data),Cd(e),e}const rc=new Map,Td=new Set;function rr(e,t){let n=rc.get(e);return n||(n=t(),rc.set(e,n),Td.add(n)),n}const es=(e,t,n)=>{const i=Fn(t,n);i!==void 0&&e.add(i)};class Ab{constructor(t){this._config=Cb(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=Ad(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),Cd(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return rr(t,()=>[[`datasets.${t}`,""]])}datasetAnimationScopeKeys(t,n){return rr(`${t}.transition.${n}`,()=>[[`datasets.${t}.transitions.${n}`,`transitions.${n}`],[`datasets.${t}`,""]])}datasetElementScopeKeys(t,n){return rr(`${t}-${n}`,()=>[[`datasets.${t}.elements.${n}`,`datasets.${t}`,`elements.${n}`,""]])}pluginScopeKeys(t){const n=t.id,i=this.type;return rr(`${i}-plugin-${n}`,()=>[[`plugins.${n}`,...t.additionalOptionScopes||[]]])}_cachedScopes(t,n){const i=this._scopeCache;let s=i.get(t);return(!s||n)&&(s=new Map,i.set(t,s)),s}getOptionScopes(t,n,i){const{options:s,type:r}=this,o=this._cachedScopes(t,i),a=o.get(n);if(a)return a;const l=new Set;n.forEach(u=>{t&&(l.add(t),u.forEach(d=>es(l,t,d))),u.forEach(d=>es(l,s,d)),u.forEach(d=>es(l,hi[r]||{},d)),u.forEach(d=>es(l,kt,d)),u.forEach(d=>es(l,ia,d))});const c=Array.from(l);return c.length===0&&c.push(Object.create(null)),Td.has(n)&&o.set(n,c),c}chartOptionScopes(){const{options:t,type:n}=this;return[t,hi[n]||{},kt.datasets[n]||{},{type:n},kt,ia]}resolveNamedOptions(t,n,i,s=[""]){const r={$shared:!0},{resolver:o,subPrefixes:a}=oc(this._resolverCache,t,s);let l=o;if(Db(o,n)){r.$shared=!1,i=zn(i)?i():i;const c=this.createResolver(t,i,a);l=Wi(o,i,c)}for(const c of n)r[c]=l[c];return r}createResolver(t,n,i=[""],s){const{resolver:r}=oc(this._resolverCache,t,i);return ot(n)?Wi(r,n,void 0,s):r}}function oc(e,t,n){let i=e.get(t);i||(i=new Map,e.set(t,i));const s=n.join();let r=i.get(s);return r||(r={resolver:Ya(t,n),subPrefixes:n.filter(a=>!a.toLowerCase().includes("hover"))},i.set(s,r)),r}const Tb=e=>ot(e)&&Object.getOwnPropertyNames(e).some(t=>zn(e[t]));function Db(e,t){const{isScriptable:n,isIndexable:i}=ad(e);for(const s of t){const r=n(s),o=i(s),a=(o||r)&&e[s];if(r&&(zn(a)||Tb(a))||o&&yt(a))return!0}return!1}var Eb="4.5.1";const Ob=["top","bottom","left","right","chartArea"];function ac(e,t){return e==="top"||e==="bottom"||Ob.indexOf(e)===-1&&t==="x"}function lc(e,t){return function(n,i){return n[e]===i[e]?n[t]-i[t]:n[e]-i[e]}}function cc(e){const t=e.chart,n=t.options.animation;t.notifyPlugins("afterRender"),pt(n&&n.onComplete,[e],t)}function Lb(e){const t=e.chart,n=t.options.animation;pt(n&&n.onProgress,[e],t)}function Dd(e){return Ka()&&typeof e=="string"?e=document.getElementById(e):e&&e.length&&(e=e[0]),e&&e.canvas&&(e=e.canvas),e}const Ar={},uc=e=>{const t=Dd(e);return Object.values(Ar).filter(n=>n.canvas===t).pop()};function Rb(e,t,n){const i=Object.keys(e);for(const s of i){const r=+s;if(r>=t){const o=e[s];delete e[s],(n>0||r>t)&&(e[r+n]=o)}}}function Ib(e,t,n,i){return!n||e.type==="mouseout"?null:i?t:e}class $e{static register(...t){ze.add(...t),dc()}static unregister(...t){ze.remove(...t),dc()}constructor(t,n){const i=this.config=new Ab(n),s=Dd(t),r=uc(s);if(r)throw new Error("Canvas is already in use. Chart with ID '"+r.id+"' must be destroyed before the canvas with ID '"+r.canvas.id+"' can be reused.");const o=i.createResolver(i.chartOptionScopes(),this.getContext());this.platform=new(i.platform||$_(s)),this.platform.updateConfig(i);const a=this.platform.acquireContext(s,o.aspectRatio),l=a&&a.canvas,c=l&&l.height,u=l&&l.width;if(this.id=Fv(),this.ctx=a,this.canvas=l,this.width=u,this.height=c,this._options=o,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new mb,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=$v(d=>this.update(d),o.resizeDelay||0),this._dataChanges=[],Ar[this.id]=this,!a||!l){console.error("Failed to create chart: can't acquire context from the given item");return}Ze.listen(this,"complete",cc),Ze.listen(this,"progress",Lb),this._initialize(),this.attached&&this.update()}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:n},width:i,height:s,_aspectRatio:r}=this;return it(t)?n&&r?r:s?i/s:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}get registry(){return ze}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():Ll(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return Dl(this.canvas,this.ctx),this}stop(){return Ze.stop(this),this}resize(t,n){Ze.running(this)?this._resizeBeforeDraw={width:t,height:n}:this._resize(t,n)}_resize(t,n){const i=this.options,s=this.canvas,r=i.maintainAspectRatio&&this.aspectRatio,o=this.platform.getMaximumSize(s,t,n,r),a=i.devicePixelRatio||this.platform.getDevicePixelRatio(),l=this.width?"resize":"attach";this.width=o.width,this.height=o.height,this._aspectRatio=this.aspectRatio,Ll(this,a,!0)&&(this.notifyPlugins("resize",{size:o}),pt(i.onResize,[this,o],this),this.attached&&this._doResize(l)&&this.render())}ensureScalesHaveIDs(){const n=this.options.scales||{};gt(n,(i,s)=>{i.id=s})}buildOrUpdateScales(){const t=this.options,n=t.scales,i=this.scales,s=Object.keys(i).reduce((o,a)=>(o[a]=!1,o),{});let r=[];n&&(r=r.concat(Object.keys(n).map(o=>{const a=n[o],l=aa(o,a),c=l==="r",u=l==="x";return{options:a,dposition:c?"chartArea":u?"bottom":"left",dtype:c?"radialLinear":u?"category":"linear"}}))),gt(r,o=>{const a=o.options,l=a.id,c=aa(l,a),u=Q(a.type,o.dtype);(a.position===void 0||ac(a.position,c)!==ac(o.dposition))&&(a.position=o.dposition),s[l]=!0;let d=null;if(l in i&&i[l].type===u)d=i[l];else{const h=ze.getScale(u);d=new h({id:l,type:u,ctx:this.ctx,chart:this}),i[d.id]=d}d.init(a,t)}),gt(s,(o,a)=>{o||delete i[a]}),gt(i,o=>{Kt.configure(this,o,o.options),Kt.addBox(this,o)})}_updateMetasets(){const t=this._metasets,n=this.data.datasets.length,i=t.length;if(t.sort((s,r)=>s.index-r.index),i>n){for(let s=n;sn.length&&delete this._stacks,t.forEach((i,s)=>{n.filter(r=>r===i._dataset).length===0&&this._destroyDatasetMeta(s)})}buildOrUpdateControllers(){const t=[],n=this.data.datasets;let i,s;for(this._removeUnreferencedMetasets(),i=0,s=n.length;i{this.getDatasetMeta(n).controller.reset()},this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const n=this.config;n.update();const i=this._options=n.createResolver(n.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!i.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0})===!1)return;const r=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let o=0;for(let c=0,u=this.data.datasets.length;c{c.reset()}),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(lc("z","_idx"));const{_active:a,_lastEvent:l}=this;l?this._eventHandler(l,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){gt(this.scales,t=>{Kt.removeBox(this,t)}),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,n=new Set(Object.keys(this._listeners)),i=new Set(t.events);(!yl(n,i)||!!this._responsiveListeners!==t.responsive)&&(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,n=this._getUniformDataChanges()||[];for(const{method:i,start:s,count:r}of n){const o=i==="_removeElements"?-r:r;Rb(t,s,o)}}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const n=this.data.datasets.length,i=r=>new Set(t.filter(o=>o[0]===r).map((o,a)=>a+","+o.splice(1).join(","))),s=i(0);for(let r=1;rr.split(",")).map(r=>({method:r[1],start:+r[2],count:+r[3]}))}_updateLayout(t){if(this.notifyPlugins("beforeLayout",{cancelable:!0})===!1)return;Kt.update(this,this.width,this.height,t);const n=this.chartArea,i=n.width<=0||n.height<=0;this._layers=[],gt(this.boxes,s=>{i&&s.position==="chartArea"||(s.configure&&s.configure(),this._layers.push(...s._layers()))},this),this._layers.forEach((s,r)=>{s._idx=r}),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})!==!1){for(let n=0,i=this.data.datasets.length;n=0;--n)this._drawDataset(t[n]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const n=this.ctx,i={meta:t,index:t.index,cancelable:!0},s=_d(this,t);this.notifyPlugins("beforeDatasetDraw",i)!==!1&&(s&&no(n,s),t.controller.draw(),s&&io(n),i.cancelable=!1,this.notifyPlugins("afterDatasetDraw",i))}isPointInArea(t){return an(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,n,i,s){const r=O_.modes[n];return typeof r=="function"?r(this,t,i,s):[]}getDatasetMeta(t){const n=this.data.datasets[t],i=this._metasets;let s=i.filter(r=>r&&r._dataset===n).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:n&&n.order||0,index:t,_dataset:n,_parsed:[],_sorted:!1},i.push(s)),s}getContext(){return this.$context||(this.$context=jn(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const n=this.data.datasets[t];if(!n)return!1;const i=this.getDatasetMeta(t);return typeof i.hidden=="boolean"?!i.hidden:!n.hidden}setDatasetVisibility(t,n){const i=this.getDatasetMeta(t);i.hidden=!n}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,n,i){const s=i?"show":"hide",r=this.getDatasetMeta(t),o=r.controller._resolveAnimations(void 0,s);Ms(n)?(r.data[n].hidden=!i,this.update()):(this.setDatasetVisibility(t,i),o.update(r,{visible:i}),this.update(a=>a.datasetIndex===t?s:void 0))}hide(t,n){this._updateVisibility(t,n,!1)}show(t,n){this._updateVisibility(t,n,!0)}_destroyDatasetMeta(t){const n=this._metasets[t];n&&n.controller&&n.controller._destroy(),delete this._metasets[t]}_stop(){let t,n;for(this.stop(),Ze.remove(this),t=0,n=this.data.datasets.length;t{n.addEventListener(this,r,o),t[r]=o},s=(r,o,a)=>{r.offsetX=o,r.offsetY=a,this._eventHandler(r)};gt(this.options.events,r=>i(r,s))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,n=this.platform,i=(l,c)=>{n.addEventListener(this,l,c),t[l]=c},s=(l,c)=>{t[l]&&(n.removeEventListener(this,l,c),delete t[l])},r=(l,c)=>{this.canvas&&this.resize(l,c)};let o;const a=()=>{s("attach",a),this.attached=!0,this.resize(),i("resize",r),i("detach",o)};o=()=>{this.attached=!1,s("resize",r),this._stop(),this._resize(0,0),i("attach",a)},n.isAttached(this.canvas)?a():o()}unbindEvents(){gt(this._listeners,(t,n)=>{this.platform.removeEventListener(this,n,t)}),this._listeners={},gt(this._responsiveListeners,(t,n)=>{this.platform.removeEventListener(this,n,t)}),this._responsiveListeners=void 0}updateHoverStyle(t,n,i){const s=i?"set":"remove";let r,o,a,l;for(n==="dataset"&&(r=this.getDatasetMeta(t[0].datasetIndex),r.controller["_"+s+"DatasetHoverStyle"]()),a=0,l=t.length;a{const a=this.getDatasetMeta(r);if(!a)throw new Error("No dataset found at index "+r);return{datasetIndex:r,element:a.data[o],index:o}});!Ir(i,n)&&(this._active=i,this._lastEvent=null,this._updateHoverStyles(i,n))}notifyPlugins(t,n,i){return this._plugins.notify(this,t,n,i)}isPluginEnabled(t){return this._plugins._cache.filter(n=>n.plugin.id===t).length===1}_updateHoverStyles(t,n,i){const s=this.options.hover,r=(l,c)=>l.filter(u=>!c.some(d=>u.datasetIndex===d.datasetIndex&&u.index===d.index)),o=r(n,t),a=i?t:r(t,n);o.length&&this.updateHoverStyle(o,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(t,n){const i={event:t,replay:n,cancelable:!0,inChartArea:this.isPointInArea(t)},s=o=>(o.options.events||this.options.events).includes(t.native.type);if(this.notifyPlugins("beforeEvent",i,s)===!1)return;const r=this._handleEvent(t,n,i.inChartArea);return i.cancelable=!1,this.notifyPlugins("afterEvent",i,s),(r||i.changed)&&this.render(),this}_handleEvent(t,n,i){const{_active:s=[],options:r}=this,o=n,a=this._getActiveElements(t,s,i,o),l=Wv(t),c=Ib(t,this._lastEvent,i,l);i&&(this._lastEvent=null,pt(r.onHover,[t,a,this],this),l&&pt(r.onClick,[t,a,this],this));const u=!Ir(a,s);return(u||n)&&(this._active=a,this._updateHoverStyles(a,s,n)),this._lastEvent=c,u}_getActiveElements(t,n,i,s){if(t.type==="mouseout")return[];if(!i)return n;const r=this.options.hover;return this.getElementsAtEventForMode(t,r.mode,r,s)}}z($e,"defaults",kt),z($e,"instances",Ar),z($e,"overrides",hi),z($e,"registry",ze),z($e,"version",Eb),z($e,"getChart",uc);function dc(){return gt($e.instances,e=>e._plugins.invalidate())}function Fb(e,t,n){const{startAngle:i,x:s,y:r,outerRadius:o,innerRadius:a,options:l}=t,{borderWidth:c,borderJoinStyle:u}=l,d=Math.min(c/o,Xt(i-n));if(e.beginPath(),e.arc(s,r,o-c/2,i+d/2,n-d/2),a>0){const h=Math.min(c/a,Xt(i-n));e.arc(s,r,a+c/2,n-h/2,i+h/2,!0)}else{const h=Math.min(c/2,o*Xt(i-n));if(u==="round")e.arc(s,r,h,n-ut/2,i+ut/2,!0);else if(u==="bevel"){const f=2*h*h,g=-f*Math.cos(n+ut/2)+s,v=-f*Math.sin(n+ut/2)+r,p=f*Math.cos(i+ut/2)+s,_=f*Math.sin(i+ut/2)+r;e.lineTo(g,v),e.lineTo(p,_)}}e.closePath(),e.moveTo(0,0),e.rect(0,0,e.canvas.width,e.canvas.height),e.clip("evenodd")}function zb(e,t,n){const{startAngle:i,pixelMargin:s,x:r,y:o,outerRadius:a,innerRadius:l}=t;let c=s/a;e.beginPath(),e.arc(r,o,a,i-c,n+c),l>s?(c=s/l,e.arc(r,o,l,n+c,i-c,!0)):e.arc(r,o,s,n+Rt,i-Rt),e.closePath(),e.clip()}function jb(e){return qa(e,["outerStart","outerEnd","innerStart","innerEnd"])}function Nb(e,t,n,i){const s=jb(e.options.borderRadius),r=(n-t)/2,o=Math.min(r,i*t/2),a=l=>{const c=(n-Math.min(r,l))*i/2;return Wt(l,0,Math.min(r,c))};return{outerStart:a(s.outerStart),outerEnd:a(s.outerEnd),innerStart:Wt(s.innerStart,0,o),innerEnd:Wt(s.innerEnd,0,o)}}function xi(e,t,n,i){return{x:n+e*Math.cos(t),y:i+e*Math.sin(t)}}function Vr(e,t,n,i,s,r){const{x:o,y:a,startAngle:l,pixelMargin:c,innerRadius:u}=t,d=Math.max(t.outerRadius+i+n-c,0),h=u>0?u+i+n+c:0;let f=0;const g=s-l;if(i){const N=u>0?u-i:0,X=d>0?d-i:0,G=(N+X)/2,B=G!==0?g*G/(G+i):g;f=(g-B)/2}const v=Math.max(.001,g*d-n/ut)/d,p=(g-v)/2,_=l+p+f,b=s-p-f,{outerStart:x,outerEnd:y,innerStart:S,innerEnd:D}=Nb(t,h,d,b-_),T=d-x,A=d-y,E=_+x/T,R=b-y/A,I=h+S,C=h+D,V=_+S/I,q=b-D/C;if(e.beginPath(),r){const N=(E+R)/2;if(e.arc(o,a,d,E,N),e.arc(o,a,d,N,R),y>0){const U=xi(A,R,o,a);e.arc(U.x,U.y,y,R,b+Rt)}const X=xi(C,b,o,a);if(e.lineTo(X.x,X.y),D>0){const U=xi(C,q,o,a);e.arc(U.x,U.y,D,b+Rt,q+Math.PI)}const G=(b-D/h+(_+S/h))/2;if(e.arc(o,a,h,b-D/h,G,!0),e.arc(o,a,h,G,_+S/h,!0),S>0){const U=xi(I,V,o,a);e.arc(U.x,U.y,S,V+Math.PI,_-Rt)}const B=xi(T,_,o,a);if(e.lineTo(B.x,B.y),x>0){const U=xi(T,E,o,a);e.arc(U.x,U.y,x,_-Rt,E)}}else{e.moveTo(o,a);const N=Math.cos(E)*d+o,X=Math.sin(E)*d+a;e.lineTo(N,X);const G=Math.cos(R)*d+o,B=Math.sin(R)*d+a;e.lineTo(G,B)}e.closePath()}function Bb(e,t,n,i,s){const{fullCircles:r,startAngle:o,circumference:a}=t;let l=t.endAngle;if(r){Vr(e,t,n,i,l,s);for(let c=0;c=ut&&f===0&&u!=="miter"&&Fb(e,t,v),r||(Vr(e,t,n,i,v,s),e.stroke())}class ki extends hn{constructor(n){super();z(this,"circumference");z(this,"endAngle");z(this,"fullCircles");z(this,"innerRadius");z(this,"outerRadius");z(this,"pixelMargin");z(this,"startAngle");this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,n&&Object.assign(this,n)}inRange(n,i,s){const r=this.getProps(["x","y"],s),{angle:o,distance:a}=Ju(r,{x:n,y:i}),{startAngle:l,endAngle:c,innerRadius:u,outerRadius:d,circumference:h}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],s),f=(this.options.spacing+this.options.borderWidth)/2,g=Q(h,c-l),v=Ps(o,l,c)&&l!==c,p=g>=wt||v,_=rn(a,u+f,d+f);return p&&_}getCenterPoint(n){const{x:i,y:s,startAngle:r,endAngle:o,innerRadius:a,outerRadius:l}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],n),{offset:c,spacing:u}=this.options,d=(r+o)/2,h=(a+l+u+c)/2;return{x:i+Math.cos(d)*h,y:s+Math.sin(d)*h}}tooltipPosition(n){return this.getCenterPoint(n)}draw(n){const{options:i,circumference:s}=this,r=(i.offset||0)/4,o=(i.spacing||0)/2,a=i.circular;if(this.pixelMargin=i.borderAlign==="inner"?.33:0,this.fullCircles=s>wt?Math.floor(s/wt):0,s===0||this.innerRadius<0||this.outerRadius<0)return;n.save();const l=(this.startAngle+this.endAngle)/2;n.translate(Math.cos(l)*r,Math.sin(l)*r);const c=1-Math.sin(Math.min(ut,s||0)),u=r*c;n.fillStyle=i.backgroundColor,n.strokeStyle=i.borderColor,Bb(n,this,u,o,a),Vb(n,this,u,o,a),n.restore()}}z(ki,"id","arc"),z(ki,"defaults",{borderAlign:"center",borderColor:"#fff",borderDash:[],borderDashOffset:0,borderJoinStyle:void 0,borderRadius:0,borderWidth:2,offset:0,spacing:0,angle:void 0,circular:!0,selfJoin:!1}),z(ki,"defaultRoutes",{backgroundColor:"backgroundColor"}),z(ki,"descriptors",{_scriptable:!0,_indexable:n=>n!=="borderDash"});function Ed(e,t,n=t){e.lineCap=Q(n.borderCapStyle,t.borderCapStyle),e.setLineDash(Q(n.borderDash,t.borderDash)),e.lineDashOffset=Q(n.borderDashOffset,t.borderDashOffset),e.lineJoin=Q(n.borderJoinStyle,t.borderJoinStyle),e.lineWidth=Q(n.borderWidth,t.borderWidth),e.strokeStyle=Q(n.borderColor,t.borderColor)}function Wb(e,t,n){e.lineTo(n.x,n.y)}function Hb(e){return e.stepped?dm:e.tension||e.cubicInterpolationMode==="monotone"?hm:Wb}function Od(e,t,n={}){const i=e.length,{start:s=0,end:r=i-1}=n,{start:o,end:a}=t,l=Math.max(s,o),c=Math.min(r,a),u=sa&&r>a;return{count:i,start:l,loop:t.loop,ilen:c(o+(c?a-y:y))%r,x=()=>{v!==p&&(e.lineTo(u,p),e.lineTo(u,v),e.lineTo(u,_))};for(l&&(f=s[b(0)],e.moveTo(f.x,f.y)),h=0;h<=a;++h){if(f=s[b(h)],f.skip)continue;const y=f.x,S=f.y,D=y|0;D===g?(Sp&&(p=S),u=(d*u+y)/++d):(x(),e.lineTo(y,S),g=D,d=0,v=p=S),_=S}x()}function la(e){const t=e.options,n=t.borderDash&&t.borderDash.length;return!e._decimated&&!e._loop&&!t.tension&&t.cubicInterpolationMode!=="monotone"&&!t.stepped&&!n?Yb:qb}function Ub(e){return e.stepped?qm:e.tension||e.cubicInterpolationMode==="monotone"?Ym:Yn}function Xb(e,t,n,i){let s=t._path;s||(s=t._path=new Path2D,t.path(s,n,i)&&s.closePath()),Ed(e,t.options),e.stroke(s)}function Kb(e,t,n,i){const{segments:s,options:r}=t,o=la(t);for(const a of s)Ed(e,r,a.style),e.beginPath(),o(e,t,a,{start:n,end:n+i-1})&&e.closePath(),e.stroke()}const Gb=typeof Path2D=="function";function Zb(e,t,n,i){Gb&&!t.options.segment?Xb(e,t,n,i):Kb(e,t,n,i)}class Pn extends hn{constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,n){const i=this.options;if((i.tension||i.cubicInterpolationMode==="monotone")&&!i.stepped&&!this._pointsUpdated){const s=i.spanGaps?this._loop:this._fullLoop;Fm(this._points,i,t,s,n),this._pointsUpdated=!0}}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=Jm(this,this.options.segment))}first(){const t=this.segments,n=this.points;return t.length&&n[t[0].start]}last(){const t=this.segments,n=this.points,i=t.length;return i&&n[t[i-1].end]}interpolate(t,n){const i=this.options,s=t[n],r=this.points,o=md(this,{property:n,start:s,end:s});if(!o.length)return;const a=[],l=Ub(i);let c,u;for(c=0,u=o.length;ct!=="borderDash"&&t!=="fill"});function hc(e,t,n,i){const s=e.options,{[n]:r}=e.getProps([n],i);return Math.abs(t-r)e.replace("rgb(","rgba(").replace(")",", 0.5)"));function Rd(e){return ca[e%ca.length]}function Id(e){return fc[e%fc.length]}function ix(e,t){return e.borderColor=Rd(t),e.backgroundColor=Id(t),++t}function sx(e,t){return e.backgroundColor=e.data.map(()=>Rd(t++)),t}function rx(e,t){return e.backgroundColor=e.data.map(()=>Id(t++)),t}function ox(e){let t=0;return(n,i)=>{const s=e.getDatasetMeta(i).controller;s instanceof Gn?t=sx(n,t):s instanceof ms?t=rx(n,t):s&&(t=ix(n,t))}}function gc(e){let t;for(t in e)if(e[t].borderColor||e[t].backgroundColor)return!0;return!1}function ax(e){return e&&(e.borderColor||e.backgroundColor)}function lx(){return kt.borderColor!=="rgba(0,0,0,0.1)"||kt.backgroundColor!=="rgba(0,0,0,0.1)"}var cx={id:"colors",defaults:{enabled:!0,forceOverride:!1},beforeLayout(e,t,n){if(!n.enabled)return;const{data:{datasets:i},options:s}=e.config,{elements:r}=s,o=gc(i)||ax(s)||r&&gc(r)||lx();if(!n.forceOverride&&o)return;const a=ox(e);i.forEach(a)}};function ux(e,t,n,i,s){const r=s.samples||i;if(r>=n)return e.slice(t,t+n);const o=[],a=(n-2)/(r-2);let l=0;const c=t+n-1;let u=t,d,h,f,g,v;for(o[l++]=e[u],d=0;df&&(f=g,h=e[b],v=b);o[l++]=h,u=v}return o[l++]=e[c],o}function dx(e,t,n,i){let s=0,r=0,o,a,l,c,u,d,h,f,g,v;const p=[],_=t+n-1,b=e[t].x,y=e[_].x-b;for(o=t;ov&&(v=c,h=o),s=(r*s+a.x)/++r;else{const D=o-1;if(!it(d)&&!it(h)){const T=Math.min(d,h),A=Math.max(d,h);T!==f&&T!==D&&p.push({...e[T],x:s}),A!==f&&A!==D&&p.push({...e[A],x:s})}o>0&&D!==f&&p.push(e[D]),p.push(a),u=S,r=0,g=v=c,d=h=f=o}}return p}function Fd(e){if(e._decimated){const t=e._data;delete e._decimated,delete e._data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,writable:!0,value:t})}}function pc(e){e.data.datasets.forEach(t=>{Fd(t)})}function hx(e,t){const n=t.length;let i=0,s;const{iScale:r}=e,{min:o,max:a,minDefined:l,maxDefined:c}=r.getUserBounds();return l&&(i=Wt(on(t,r.axis,o).lo,0,n-1)),c?s=Wt(on(t,r.axis,a).hi+1,i,n)-i:s=n-i,{start:i,count:s}}var fx={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(e,t,n)=>{if(!n.enabled){pc(e);return}const i=e.width;e.data.datasets.forEach((s,r)=>{const{_data:o,indexAxis:a}=s,l=e.getDatasetMeta(r),c=o||s.data;if(xt([a,e.options.indexAxis])==="y"||!l.controller.supportsDecimation)return;const u=e.scales[l.xAxisID];if(u.type!=="linear"&&u.type!=="time"||e.options.parsing)return;let{start:d,count:h}=hx(l,c);const f=n.threshold||4*i;if(h<=f){Fd(s);return}it(o)&&(s._data=c,delete s.data,Object.defineProperty(s,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(v){this._data=v}}));let g;switch(n.algorithm){case"lttb":g=ux(c,d,h,i,n);break;case"min-max":g=dx(c,d,h,i);break;default:throw new Error(`Unsupported decimation algorithm '${n.algorithm}'`)}s._decimated=g})},destroy(e){pc(e)}};function gx(e,t,n){const i=e.segments,s=e.points,r=t.points,o=[];for(const a of i){let{start:l,end:c}=a;c=oo(l,c,s);const u=ua(n,s[l],s[c],a.loop);if(!t.segments){o.push({source:a,target:u,start:s[l],end:s[c]});continue}const d=md(t,u);for(const h of d){const f=ua(n,r[h.start],r[h.end],h.loop),g=vd(a,s,f);for(const v of g)o.push({source:v,target:h,start:{[n]:vc(u,f,"start",Math.max)},end:{[n]:vc(u,f,"end",Math.min)}})}}return o}function ua(e,t,n,i){if(i)return;let s=t[e],r=n[e];return e==="angle"&&(s=Xt(s),r=Xt(r)),{property:e,start:s,end:r}}function px(e,t){const{x:n=null,y:i=null}=e||{},s=t.points,r=[];return t.segments.forEach(({start:o,end:a})=>{a=oo(o,a,s);const l=s[o],c=s[a];i!==null?(r.push({x:l.x,y:i}),r.push({x:c.x,y:i})):n!==null&&(r.push({x:n,y:l.y}),r.push({x:n,y:c.y}))}),r}function oo(e,t,n){for(;t>e;t--){const i=n[t];if(!isNaN(i.x)&&!isNaN(i.y))break}return t}function vc(e,t,n,i){return e&&t?i(e[n],t[n]):e?e[n]:t?t[n]:0}function zd(e,t){let n=[],i=!1;return yt(e)?(i=!0,n=e):n=px(e,t),n.length?new Pn({points:n,options:{tension:0},_loop:i,_fullLoop:i}):null}function mc(e){return e&&e.fill!==!1}function vx(e,t,n){let s=e[t].fill;const r=[t];let o;if(!n)return s;for(;s!==!1&&r.indexOf(s)===-1;){if(!Tt(s))return s;if(o=e[s],!o)return!1;if(o.visible)return s;r.push(s),s=o.fill}return!1}function mx(e,t,n){const i=yx(e);if(ot(i))return isNaN(i.value)?!1:i;let s=parseFloat(i);return Tt(s)&&Math.floor(s)===s?_x(i[0],t,s,n):["origin","start","end","stack","shape"].indexOf(i)>=0&&i}function _x(e,t,n,i){return(e==="-"||e==="+")&&(n=t+n),n===t||n<0||n>=i?!1:n}function bx(e,t){let n=null;return e==="start"?n=t.bottom:e==="end"?n=t.top:ot(e)?n=t.getPixelForValue(e.value):t.getBasePixel&&(n=t.getBasePixel()),n}function xx(e,t,n){let i;return e==="start"?i=n:e==="end"?i=t.options.reverse?t.min:t.max:ot(e)?i=e.value:i=t.getBaseValue(),i}function yx(e){const t=e.options,n=t.fill;let i=Q(n&&n.target,n);return i===void 0&&(i=!!t.backgroundColor),i===!1||i===null?!1:i===!0?"origin":i}function wx(e){const{scale:t,index:n,line:i}=e,s=[],r=i.segments,o=i.points,a=kx(t,n);a.push(zd({x:null,y:t.bottom},i));for(let l=0;l=0;--o){const a=s[o].$filler;a&&(a.line.updateControlPoints(r,a.axis),i&&a.fill&&Io(e.ctx,a,r))}},beforeDatasetsDraw(e,t,n){if(n.drawTime!=="beforeDatasetsDraw")return;const i=e.getSortedVisibleDatasetMetas();for(let s=i.length-1;s>=0;--s){const r=i[s].$filler;mc(r)&&Io(e.ctx,r,e.chartArea)}},beforeDatasetDraw(e,t,n){const i=t.meta.$filler;!mc(i)||n.drawTime!=="beforeDatasetDraw"||Io(e.ctx,i,e.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const yc=(e,t)=>{let{boxHeight:n=t,boxWidth:i=t}=e;return e.usePointStyle&&(n=Math.min(n,t),i=e.pointStyleWidth||Math.min(i,t)),{boxWidth:i,boxHeight:n,itemHeight:Math.max(t,n)}},Rx=(e,t)=>e!==null&&t!==null&&e.datasetIndex===t.datasetIndex&&e.index===t.index;class wc extends hn{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,n,i){this.maxWidth=t,this.maxHeight=n,this._margins=i,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let n=pt(t.generateLabels,[this.chart],this)||[];t.filter&&(n=n.filter(i=>t.filter(i,this.chart.data))),t.sort&&(n=n.sort((i,s)=>t.sort(i,s,this.chart.data))),this.options.reverse&&n.reverse(),this.legendItems=n}fit(){const{options:t,ctx:n}=this;if(!t.display){this.width=this.height=0;return}const i=t.labels,s=zt(i.font),r=s.size,o=this._computeTitleHeight(),{boxWidth:a,itemHeight:l}=yc(i,r);let c,u;n.font=s.string,this.isHorizontal()?(c=this.maxWidth,u=this._fitRows(o,r,a,l)+10):(u=this.maxHeight,c=this._fitCols(o,s,a,l)+10),this.width=Math.min(c,t.maxWidth||this.maxWidth),this.height=Math.min(u,t.maxHeight||this.maxHeight)}_fitRows(t,n,i,s){const{ctx:r,maxWidth:o,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.lineWidths=[0],u=s+a;let d=t;r.textAlign="left",r.textBaseline="middle";let h=-1,f=-u;return this.legendItems.forEach((g,v)=>{const p=i+n/2+r.measureText(g.text).width;(v===0||c[c.length-1]+p+2*a>o)&&(d+=u,c[c.length-(v>0?0:1)]=0,f+=u,h++),l[v]={left:0,top:f,row:h,width:p,height:s},c[c.length-1]+=p+a}),d}_fitCols(t,n,i,s){const{ctx:r,maxHeight:o,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.columnSizes=[],u=o-t;let d=a,h=0,f=0,g=0,v=0;return this.legendItems.forEach((p,_)=>{const{itemWidth:b,itemHeight:x}=Ix(i,n,r,p,s);_>0&&f+x+2*a>u&&(d+=h+a,c.push({width:h,height:f}),g+=h+a,v++,h=f=0),l[_]={left:g,top:f,col:v,width:b,height:x},h=Math.max(h,b),f+=x+a}),d+=h,c.push({width:h,height:f}),d}adjustHitBoxes(){if(!this.options.display)return;const t=this._computeTitleHeight(),{legendHitBoxes:n,options:{align:i,labels:{padding:s},rtl:r}}=this,o=Pi(r,this.left,this.width);if(this.isHorizontal()){let a=0,l=Ut(i,this.left+s,this.right-this.lineWidths[a]);for(const c of n)a!==c.row&&(a=c.row,l=Ut(i,this.left+s,this.right-this.lineWidths[a])),c.top+=this.top+t+s,c.left=o.leftForLtr(o.x(l),c.width),l+=c.width+s}else{let a=0,l=Ut(i,this.top+t+s,this.bottom-this.columnSizes[a].height);for(const c of n)c.col!==a&&(a=c.col,l=Ut(i,this.top+t+s,this.bottom-this.columnSizes[a].height)),c.top=l,c.left+=this.left+s,c.left=o.leftForLtr(o.x(c.left),c.width),l+=c.height+s}}isHorizontal(){return this.options.position==="top"||this.options.position==="bottom"}draw(){if(this.options.display){const t=this.ctx;no(t,this),this._draw(),io(t)}}_draw(){const{options:t,columnSizes:n,lineWidths:i,ctx:s}=this,{align:r,labels:o}=t,a=kt.color,l=Pi(t.rtl,this.left,this.width),c=zt(o.font),{padding:u}=o,d=c.size,h=d/2;let f;this.drawTitle(),s.textAlign=l.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=c.string;const{boxWidth:g,boxHeight:v,itemHeight:p}=yc(o,d),_=function(D,T,A){if(isNaN(g)||g<=0||isNaN(v)||v<0)return;s.save();const E=Q(A.lineWidth,1);if(s.fillStyle=Q(A.fillStyle,a),s.lineCap=Q(A.lineCap,"butt"),s.lineDashOffset=Q(A.lineDashOffset,0),s.lineJoin=Q(A.lineJoin,"miter"),s.lineWidth=E,s.strokeStyle=Q(A.strokeStyle,a),s.setLineDash(Q(A.lineDash,[])),o.usePointStyle){const R={radius:v*Math.SQRT2/2,pointStyle:A.pointStyle,rotation:A.rotation,borderWidth:E},I=l.xPlus(D,g/2),C=T+h;rd(s,R,I,C,o.pointStyleWidth&&g)}else{const R=T+Math.max((d-v)/2,0),I=l.leftForLtr(D,g),C=oi(A.borderRadius);s.beginPath(),Object.values(C).some(V=>V!==0)?Cs(s,{x:I,y:R,w:g,h:v,radius:C}):s.rect(I,R,g,v),s.fill(),E!==0&&s.stroke()}s.restore()},b=function(D,T,A){fi(s,A.text,D,T+p/2,c,{strikethrough:A.hidden,textAlign:l.textAlign(A.textAlign)})},x=this.isHorizontal(),y=this._computeTitleHeight();x?f={x:Ut(r,this.left+u,this.right-i[0]),y:this.top+u+y,line:0}:f={x:this.left+u,y:Ut(r,this.top+y+u,this.bottom-n[0].height),line:0},fd(this.ctx,t.textDirection);const S=p+u;this.legendItems.forEach((D,T)=>{s.strokeStyle=D.fontColor,s.fillStyle=D.fontColor;const A=s.measureText(D.text).width,E=l.textAlign(D.textAlign||(D.textAlign=o.textAlign)),R=g+h+A;let I=f.x,C=f.y;l.setWidth(this.width),x?T>0&&I+R+u>this.right&&(C=f.y+=S,f.line++,I=f.x=Ut(r,this.left+u,this.right-i[f.line])):T>0&&C+S>this.bottom&&(I=f.x=I+n[f.line].width+u,f.line++,C=f.y=Ut(r,this.top+y+u,this.bottom-n[f.line].height));const V=l.x(I);if(_(V,C,D),I=tm(E,I+g+h,x?I+R:this.right,t.rtl),b(l.x(I),C,D),x)f.x+=R+u;else if(typeof D.text!="string"){const q=c.lineHeight;f.y+=Nd(D,q)+u}else f.y+=S}),gd(this.ctx,t.textDirection)}drawTitle(){const t=this.options,n=t.title,i=zt(n.font),s=Yt(n.padding);if(!n.display)return;const r=Pi(t.rtl,this.left,this.width),o=this.ctx,a=n.position,l=i.size/2,c=s.top+l;let u,d=this.left,h=this.width;if(this.isHorizontal())h=Math.max(...this.lineWidths),u=this.top+c,d=Ut(t.align,d,this.right-h);else{const g=this.columnSizes.reduce((v,p)=>Math.max(v,p.height),0);u=c+Ut(t.align,this.top,this.bottom-g-t.labels.padding-this._computeTitleHeight())}const f=Ut(a,d,d+h);o.textAlign=r.textAlign(Wa(a)),o.textBaseline="middle",o.strokeStyle=n.color,o.fillStyle=n.color,o.font=i.string,fi(o,n.text,f,u,i)}_computeTitleHeight(){const t=this.options.title,n=zt(t.font),i=Yt(t.padding);return t.display?n.lineHeight+i.height:0}_getLegendItemAt(t,n){let i,s,r;if(rn(t,this.left,this.right)&&rn(n,this.top,this.bottom)){for(r=this.legendHitBoxes,i=0;ir.length>o.length?r:o)),t+n.size/2+i.measureText(s).width}function zx(e,t,n){let i=e;return typeof t.text!="string"&&(i=Nd(t,n)),i}function Nd(e,t){const n=e.text?e.text.length:0;return t*n}function jx(e,t){return!!((e==="mousemove"||e==="mouseout")&&(t.onHover||t.onLeave)||t.onClick&&(e==="click"||e==="mouseup"))}var Nx={id:"legend",_element:wc,start(e,t,n){const i=e.legend=new wc({ctx:e.ctx,options:n,chart:e});Kt.configure(e,i,n),Kt.addBox(e,i)},stop(e){Kt.removeBox(e,e.legend),delete e.legend},beforeUpdate(e,t,n){const i=e.legend;Kt.configure(e,i,n),i.options=n},afterUpdate(e){const t=e.legend;t.buildLabels(),t.adjustHitBoxes()},afterEvent(e,t){t.replay||e.legend.handleEvent(t.event)},defaults:{display:!0,position:"top",align:"center",fullSize:!0,reverse:!1,weight:1e3,onClick(e,t,n){const i=t.datasetIndex,s=n.chart;s.isDatasetVisible(i)?(s.hide(i),t.hidden=!0):(s.show(i),t.hidden=!1)},onHover:null,onLeave:null,labels:{color:e=>e.chart.options.color,boxWidth:40,padding:10,generateLabels(e){const t=e.data.datasets,{labels:{usePointStyle:n,pointStyle:i,textAlign:s,color:r,useBorderRadius:o,borderRadius:a}}=e.legend.options;return e._getSortedDatasetMetas().map(l=>{const c=l.controller.getStyle(n?0:void 0),u=Yt(c.borderWidth);return{text:t[l.index].label,fillStyle:c.backgroundColor,fontColor:r,hidden:!l.visible,lineCap:c.borderCapStyle,lineDash:c.borderDash,lineDashOffset:c.borderDashOffset,lineJoin:c.borderJoinStyle,lineWidth:(u.width+u.height)/4,strokeStyle:c.borderColor,pointStyle:i||c.pointStyle,rotation:c.rotation,textAlign:s||c.textAlign,borderRadius:o&&(a||c.borderRadius),datasetIndex:l.index}},this)}},title:{color:e=>e.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:e=>!e.startsWith("on"),labels:{_scriptable:e=>!["generateLabels","filter","sort"].includes(e)}}};class Ja extends hn{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,n){const i=this.options;if(this.left=0,this.top=0,!i.display){this.width=this.height=this.right=this.bottom=0;return}this.width=this.right=t,this.height=this.bottom=n;const s=yt(i.text)?i.text.length:1;this._padding=Yt(i.padding);const r=s*zt(i.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=r:this.width=r}isHorizontal(){const t=this.options.position;return t==="top"||t==="bottom"}_drawArgs(t){const{top:n,left:i,bottom:s,right:r,options:o}=this,a=o.align;let l=0,c,u,d;return this.isHorizontal()?(u=Ut(a,i,r),d=n+t,c=r-i):(o.position==="left"?(u=i+t,d=Ut(a,s,n),l=ut*-.5):(u=r-t,d=Ut(a,n,s),l=ut*.5),c=s-n),{titleX:u,titleY:d,maxWidth:c,rotation:l}}draw(){const t=this.ctx,n=this.options;if(!n.display)return;const i=zt(n.font),r=i.lineHeight/2+this._padding.top,{titleX:o,titleY:a,maxWidth:l,rotation:c}=this._drawArgs(r);fi(t,n.text,0,0,i,{color:n.color,maxWidth:l,rotation:c,textAlign:Wa(n.align),textBaseline:"middle",translation:[o,a]})}}function Bx(e,t){const n=new Ja({ctx:e.ctx,options:t,chart:e});Kt.configure(e,n,t),Kt.addBox(e,n),e.titleBlock=n}var Vx={id:"title",_element:Ja,start(e,t,n){Bx(e,n)},stop(e){const t=e.titleBlock;Kt.removeBox(e,t),delete e.titleBlock},beforeUpdate(e,t,n){const i=e.titleBlock;Kt.configure(e,i,n),i.options=n},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const or=new WeakMap;var Wx={id:"subtitle",start(e,t,n){const i=new Ja({ctx:e.ctx,options:n,chart:e});Kt.configure(e,i,n),Kt.addBox(e,i),or.set(e,i)},stop(e){Kt.removeBox(e,or.get(e)),or.delete(e)},beforeUpdate(e,t,n){const i=or.get(e);Kt.configure(e,i,n),i.options=n},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const ls={average(e){if(!e.length)return!1;let t,n,i=new Set,s=0,r=0;for(t=0,n=e.length;ta+l)/i.size,y:s/r}},nearest(e,t){if(!e.length)return!1;let n=t.x,i=t.y,s=Number.POSITIVE_INFINITY,r,o,a;for(r=0,o=e.length;r-1?e.split(` +`):e}function Hx(e,t){const{element:n,datasetIndex:i,index:s}=t,r=e.getDatasetMeta(i).controller,{label:o,value:a}=r.getLabelAndValue(s);return{chart:e,label:o,parsed:r.getParsed(s),raw:e.data.datasets[i].data[s],formattedValue:a,dataset:r.getDataset(),dataIndex:s,datasetIndex:i,element:n}}function kc(e,t){const n=e.chart.ctx,{body:i,footer:s,title:r}=e,{boxWidth:o,boxHeight:a}=t,l=zt(t.bodyFont),c=zt(t.titleFont),u=zt(t.footerFont),d=r.length,h=s.length,f=i.length,g=Yt(t.padding);let v=g.height,p=0,_=i.reduce((y,S)=>y+S.before.length+S.lines.length+S.after.length,0);if(_+=e.beforeBody.length+e.afterBody.length,d&&(v+=d*c.lineHeight+(d-1)*t.titleSpacing+t.titleMarginBottom),_){const y=t.displayColors?Math.max(a,l.lineHeight):l.lineHeight;v+=f*y+(_-f)*l.lineHeight+(_-1)*t.bodySpacing}h&&(v+=t.footerMarginTop+h*u.lineHeight+(h-1)*t.footerSpacing);let b=0;const x=function(y){p=Math.max(p,n.measureText(y).width+b)};return n.save(),n.font=c.string,gt(e.title,x),n.font=l.string,gt(e.beforeBody.concat(e.afterBody),x),b=t.displayColors?o+2+t.boxPadding:0,gt(i,y=>{gt(y.before,x),gt(y.lines,x),gt(y.after,x)}),b=0,n.font=u.string,gt(e.footer,x),n.restore(),p+=g.width,{width:p,height:v}}function qx(e,t){const{y:n,height:i}=t;return ne.height-i/2?"bottom":"center"}function Yx(e,t,n,i){const{x:s,width:r}=i,o=n.caretSize+n.caretPadding;if(e==="left"&&s+r+o>t.width||e==="right"&&s-r-o<0)return!0}function Ux(e,t,n,i){const{x:s,width:r}=n,{width:o,chartArea:{left:a,right:l}}=e;let c="center";return i==="center"?c=s<=(a+l)/2?"left":"right":s<=r/2?c="left":s>=o-r/2&&(c="right"),Yx(c,e,t,n)&&(c="center"),c}function Sc(e,t,n){const i=n.yAlign||t.yAlign||qx(e,n);return{xAlign:n.xAlign||t.xAlign||Ux(e,t,n,i),yAlign:i}}function Xx(e,t){let{x:n,width:i}=e;return t==="right"?n-=i:t==="center"&&(n-=i/2),n}function Kx(e,t,n){let{y:i,height:s}=e;return t==="top"?i+=n:t==="bottom"?i-=s+n:i-=s/2,i}function Mc(e,t,n,i){const{caretSize:s,caretPadding:r,cornerRadius:o}=e,{xAlign:a,yAlign:l}=n,c=s+r,{topLeft:u,topRight:d,bottomLeft:h,bottomRight:f}=oi(o);let g=Xx(t,a);const v=Kx(t,l,c);return l==="center"?a==="left"?g+=c:a==="right"&&(g-=c):a==="left"?g-=Math.max(u,h)+s:a==="right"&&(g+=Math.max(d,f)+s),{x:Wt(g,0,i.width-t.width),y:Wt(v,0,i.height-t.height)}}function ar(e,t,n){const i=Yt(n.padding);return t==="center"?e.x+e.width/2:t==="right"?e.x+e.width-i.right:e.x+i.left}function Pc(e){return Fe([],Je(e))}function Gx(e,t,n){return jn(e,{tooltip:t,tooltipItems:n,type:"tooltip"})}function Cc(e,t){const n=t&&t.dataset&&t.dataset.tooltip&&t.dataset.tooltip.callbacks;return n?e.override(n):e}const Bd={beforeTitle:Ge,title(e){if(e.length>0){const t=e[0],n=t.chart.data.labels,i=n?n.length:0;if(this&&this.options&&this.options.mode==="dataset")return t.dataset.label||"";if(t.label)return t.label;if(i>0&&t.dataIndex"u"?Bd[t].call(n,i):s}class da extends hn{constructor(t){super(),this.opacity=0,this._active=[],this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.chart=t.chart,this.options=t.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(t){this.options=t,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){const t=this._cachedAnimations;if(t)return t;const n=this.chart,i=this.options.setContext(this.getContext()),s=i.enabled&&n.options.animation&&i.animations,r=new bd(this.chart,s);return s._cacheable&&(this._cachedAnimations=Object.freeze(r)),r}getContext(){return this.$context||(this.$context=Gx(this.chart.getContext(),this,this._tooltipItems))}getTitle(t,n){const{callbacks:i}=n,s=Qt(i,"beforeTitle",this,t),r=Qt(i,"title",this,t),o=Qt(i,"afterTitle",this,t);let a=[];return a=Fe(a,Je(s)),a=Fe(a,Je(r)),a=Fe(a,Je(o)),a}getBeforeBody(t,n){return Pc(Qt(n.callbacks,"beforeBody",this,t))}getBody(t,n){const{callbacks:i}=n,s=[];return gt(t,r=>{const o={before:[],lines:[],after:[]},a=Cc(i,r);Fe(o.before,Je(Qt(a,"beforeLabel",this,r))),Fe(o.lines,Qt(a,"label",this,r)),Fe(o.after,Je(Qt(a,"afterLabel",this,r))),s.push(o)}),s}getAfterBody(t,n){return Pc(Qt(n.callbacks,"afterBody",this,t))}getFooter(t,n){const{callbacks:i}=n,s=Qt(i,"beforeFooter",this,t),r=Qt(i,"footer",this,t),o=Qt(i,"afterFooter",this,t);let a=[];return a=Fe(a,Je(s)),a=Fe(a,Je(r)),a=Fe(a,Je(o)),a}_createItems(t){const n=this._active,i=this.chart.data,s=[],r=[],o=[];let a=[],l,c;for(l=0,c=n.length;lt.filter(u,d,h,i))),t.itemSort&&(a=a.sort((u,d)=>t.itemSort(u,d,i))),gt(a,u=>{const d=Cc(t.callbacks,u);s.push(Qt(d,"labelColor",this,u)),r.push(Qt(d,"labelPointStyle",this,u)),o.push(Qt(d,"labelTextColor",this,u))}),this.labelColors=s,this.labelPointStyles=r,this.labelTextColors=o,this.dataPoints=a,a}update(t,n){const i=this.options.setContext(this.getContext()),s=this._active;let r,o=[];if(!s.length)this.opacity!==0&&(r={opacity:0});else{const a=ls[i.position].call(this,s,this._eventPosition);o=this._createItems(i),this.title=this.getTitle(o,i),this.beforeBody=this.getBeforeBody(o,i),this.body=this.getBody(o,i),this.afterBody=this.getAfterBody(o,i),this.footer=this.getFooter(o,i);const l=this._size=kc(this,i),c=Object.assign({},a,l),u=Sc(this.chart,i,c),d=Mc(i,c,u,this.chart);this.xAlign=u.xAlign,this.yAlign=u.yAlign,r={opacity:1,x:d.x,y:d.y,width:l.width,height:l.height,caretX:a.x,caretY:a.y}}this._tooltipItems=o,this.$context=void 0,r&&this._resolveAnimations().update(this,r),t&&i.external&&i.external.call(this,{chart:this.chart,tooltip:this,replay:n})}drawCaret(t,n,i,s){const r=this.getCaretPosition(t,i,s);n.lineTo(r.x1,r.y1),n.lineTo(r.x2,r.y2),n.lineTo(r.x3,r.y3)}getCaretPosition(t,n,i){const{xAlign:s,yAlign:r}=this,{caretSize:o,cornerRadius:a}=i,{topLeft:l,topRight:c,bottomLeft:u,bottomRight:d}=oi(a),{x:h,y:f}=t,{width:g,height:v}=n;let p,_,b,x,y,S;return r==="center"?(y=f+v/2,s==="left"?(p=h,_=p-o,x=y+o,S=y-o):(p=h+g,_=p+o,x=y-o,S=y+o),b=p):(s==="left"?_=h+Math.max(l,u)+o:s==="right"?_=h+g-Math.max(c,d)-o:_=this.caretX,r==="top"?(x=f,y=x-o,p=_-o,b=_+o):(x=f+v,y=x+o,p=_+o,b=_-o),S=x),{x1:p,x2:_,x3:b,y1:x,y2:y,y3:S}}drawTitle(t,n,i){const s=this.title,r=s.length;let o,a,l;if(r){const c=Pi(i.rtl,this.x,this.width);for(t.x=ar(this,i.titleAlign,i),n.textAlign=c.textAlign(i.titleAlign),n.textBaseline="middle",o=zt(i.titleFont),a=i.titleSpacing,n.fillStyle=i.titleColor,n.font=o.string,l=0;lb!==0)?(t.beginPath(),t.fillStyle=r.multiKeyBackground,Cs(t,{x:v,y:g,w:c,h:l,radius:_}),t.fill(),t.stroke(),t.fillStyle=o.backgroundColor,t.beginPath(),Cs(t,{x:p,y:g+1,w:c-2,h:l-2,radius:_}),t.fill()):(t.fillStyle=r.multiKeyBackground,t.fillRect(v,g,c,l),t.strokeRect(v,g,c,l),t.fillStyle=o.backgroundColor,t.fillRect(p,g+1,c-2,l-2))}t.fillStyle=this.labelTextColors[i]}drawBody(t,n,i){const{body:s}=this,{bodySpacing:r,bodyAlign:o,displayColors:a,boxHeight:l,boxWidth:c,boxPadding:u}=i,d=zt(i.bodyFont);let h=d.lineHeight,f=0;const g=Pi(i.rtl,this.x,this.width),v=function(A){n.fillText(A,g.x(t.x+f),t.y+h/2),t.y+=h+r},p=g.textAlign(o);let _,b,x,y,S,D,T;for(n.textAlign=o,n.textBaseline="middle",n.font=d.string,t.x=ar(this,p,i),n.fillStyle=i.bodyColor,gt(this.beforeBody,v),f=a&&p!=="right"?o==="center"?c/2+u:c+2+u:0,y=0,D=s.length;y0&&n.stroke()}_updateAnimationTarget(t){const n=this.chart,i=this.$animations,s=i&&i.x,r=i&&i.y;if(s||r){const o=ls[t.position].call(this,this._active,this._eventPosition);if(!o)return;const a=this._size=kc(this,t),l=Object.assign({},o,this._size),c=Sc(n,t,l),u=Mc(t,l,c,n);(s._to!==u.x||r._to!==u.y)&&(this.xAlign=c.xAlign,this.yAlign=c.yAlign,this.width=a.width,this.height=a.height,this.caretX=o.x,this.caretY=o.y,this._resolveAnimations().update(this,u))}}_willRender(){return!!this.opacity}draw(t){const n=this.options.setContext(this.getContext());let i=this.opacity;if(!i)return;this._updateAnimationTarget(n);const s={width:this.width,height:this.height},r={x:this.x,y:this.y};i=Math.abs(i)<.001?0:i;const o=Yt(n.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;n.enabled&&a&&(t.save(),t.globalAlpha=i,this.drawBackground(r,t,s,n),fd(t,n.textDirection),r.y+=o.top,this.drawTitle(r,t,n),this.drawBody(r,t,n),this.drawFooter(r,t,n),gd(t,n.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,n){const i=this._active,s=t.map(({datasetIndex:a,index:l})=>{const c=this.chart.getDatasetMeta(a);if(!c)throw new Error("Cannot find a dataset at index "+a);return{datasetIndex:a,element:c.data[l],index:l}}),r=!Ir(i,s),o=this._positionChanged(s,n);(r||o)&&(this._active=s,this._eventPosition=n,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,n,i=!0){if(n&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,r=this._active||[],o=this._getActiveElements(t,r,n,i),a=this._positionChanged(o,t),l=n||!Ir(o,r)||a;return l&&(this._active=o,(s.enabled||s.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,n))),l}_getActiveElements(t,n,i,s){const r=this.options;if(t.type==="mouseout")return[];if(!s)return n.filter(a=>this.chart.data.datasets[a.datasetIndex]&&this.chart.getDatasetMeta(a.datasetIndex).controller.getParsed(a.index)!==void 0);const o=this.chart.getElementsAtEventForMode(t,r.mode,r,i);return r.reverse&&o.reverse(),o}_positionChanged(t,n){const{caretX:i,caretY:s,options:r}=this,o=ls[r.position].call(this,t,n);return o!==!1&&(i!==o.x||s!==o.y)}}z(da,"positioners",ls);var Zx={id:"tooltip",_element:da,positioners:ls,afterInit(e,t,n){n&&(e.tooltip=new da({chart:e,options:n}))},beforeUpdate(e,t,n){e.tooltip&&e.tooltip.initialize(n)},reset(e,t,n){e.tooltip&&e.tooltip.initialize(n)},afterDraw(e){const t=e.tooltip;if(t&&t._willRender()){const n={tooltip:t};if(e.notifyPlugins("beforeTooltipDraw",{...n,cancelable:!0})===!1)return;t.draw(e.ctx),e.notifyPlugins("afterTooltipDraw",n)}},afterEvent(e,t){if(e.tooltip){const n=t.replay;e.tooltip.handleEvent(t.event,n,t.inChartArea)&&(t.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(e,t)=>t.bodyFont.size,boxWidth:(e,t)=>t.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:Bd},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:e=>e!=="filter"&&e!=="itemSort"&&e!=="external",_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]},Jx=Object.freeze({__proto__:null,Colors:cx,Decimation:fx,Filler:Lx,Legend:Nx,SubTitle:Wx,Title:Vx,Tooltip:Zx});const Qx=(e,t,n,i)=>(typeof t=="string"?(n=e.push(t)-1,i.unshift({index:n,label:t})):isNaN(t)&&(n=null),n);function $x(e,t,n,i){const s=e.indexOf(t);if(s===-1)return Qx(e,t,n,i);const r=e.lastIndexOf(t);return s!==r?n:s}const ty=(e,t)=>e===null?null:Wt(Math.round(e),0,t);function Ac(e){const t=this.getLabels();return e>=0&&en.length-1?null:this.getPixelForValue(n[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}}z(ha,"id","category"),z(ha,"defaults",{ticks:{callback:Ac}});function ey(e,t){const n=[],{bounds:s,step:r,min:o,max:a,precision:l,count:c,maxTicks:u,maxDigits:d,includeBounds:h}=e,f=r||1,g=u-1,{min:v,max:p}=t,_=!it(o),b=!it(a),x=!it(c),y=(p-v)/(d+1);let S=kl((p-v)/g/f)*f,D,T,A,E;if(S<1e-14&&!_&&!b)return[{value:v},{value:p}];E=Math.ceil(p/S)-Math.floor(v/S),E>g&&(S=kl(E*S/g/f)*f),it(l)||(D=Math.pow(10,l),S=Math.ceil(S*D)/D),s==="ticks"?(T=Math.floor(v/S)*S,A=Math.ceil(p/S)*S):(T=v,A=p),_&&b&&r&&Xv((a-o)/r,S/1e3)?(E=Math.round(Math.min((a-o)/S,u)),S=(a-o)/E,T=o,A=a):x?(T=_?o:T,A=b?a:A,E=c-1,S=(A-T)/E):(E=(A-T)/S,gs(E,Math.round(E),S/1e3)?E=Math.round(E):E=Math.ceil(E));const R=Math.max(Sl(S),Sl(T));D=Math.pow(10,it(l)?R:l),T=Math.round(T*D)/D,A=Math.round(A*D)/D;let I=0;for(_&&(h&&T!==o?(n.push({value:o}),Ta)break;n.push({value:C})}return b&&h&&A!==a?n.length&&gs(n[n.length-1].value,a,Tc(a,y,e))?n[n.length-1].value=a:n.push({value:a}):(!b||A===a)&&n.push({value:A}),n}function Tc(e,t,{horizontal:n,minRotation:i}){const s=De(i),r=(n?Math.sin(s):Math.cos(s))||.001,o=.75*t*(""+e).length;return Math.min(t/r,o)}class Wr extends pi{constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._endValue=void 0,this._valueRange=0}parse(t,n){return it(t)||(typeof t=="number"||t instanceof Number)&&!isFinite(+t)?null:+t}handleTickRangeOptions(){const{beginAtZero:t}=this.options,{minDefined:n,maxDefined:i}=this.getUserBounds();let{min:s,max:r}=this;const o=l=>s=n?s:l,a=l=>r=i?r:l;if(t){const l=He(s),c=He(r);l<0&&c<0?a(0):l>0&&c>0&&o(0)}if(s===r){let l=r===0?1:Math.abs(r*.05);a(r+l),t||o(s-l)}this.min=s,this.max=r}getTickLimit(){const t=this.options.ticks;let{maxTicksLimit:n,stepSize:i}=t,s;return i?(s=Math.ceil(this.max/i)-Math.floor(this.min/i)+1,s>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${i} would result generating up to ${s} ticks. Limiting to 1000.`),s=1e3)):(s=this.computeTickLimit(),n=n||11),n&&(s=Math.min(n,s)),s}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this.options,n=t.ticks;let i=this.getTickLimit();i=Math.max(2,i);const s={maxTicks:i,bounds:t.bounds,min:t.min,max:t.max,precision:n.precision,step:n.stepSize,count:n.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:n.minRotation||0,includeBounds:n.includeBounds!==!1},r=this._range||this,o=ey(s,r);return t.bounds==="ticks"&&Zu(o,this,"value"),t.reverse?(o.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),o}configure(){const t=this.ticks;let n=this.min,i=this.max;if(super.configure(),this.options.offset&&t.length){const s=(i-n)/Math.max(t.length-1,1)/2;n-=s,i+=s}this._startValue=n,this._endValue=i,this._valueRange=i-n}getLabelForValue(t){return Ys(t,this.chart.options.locale,this.options.ticks.format)}}class fa extends Wr{determineDataLimits(){const{min:t,max:n}=this.getMinMax(!0);this.min=Tt(t)?t:0,this.max=Tt(n)?n:1,this.handleTickRangeOptions()}computeTickLimit(){const t=this.isHorizontal(),n=t?this.width:this.height,i=De(this.options.ticks.minRotation),s=(t?Math.sin(i):Math.cos(i))||.001,r=this._resolveTickFontOptions(0);return Math.ceil(n/Math.min(40,r.lineHeight/s))}getPixelForValue(t){return t===null?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}}z(fa,"id","linear"),z(fa,"defaults",{ticks:{callback:eo.formatters.numeric}});const Ts=e=>Math.floor(Sn(e)),Hn=(e,t)=>Math.pow(10,Ts(e)+t);function Dc(e){return e/Math.pow(10,Ts(e))===1}function Ec(e,t,n){const i=Math.pow(10,n),s=Math.floor(e/i);return Math.ceil(t/i)-s}function ny(e,t){const n=t-e;let i=Ts(n);for(;Ec(e,t,i)>10;)i++;for(;Ec(e,t,i)<10;)i--;return Math.min(i,Ts(e))}function iy(e,{min:t,max:n}){t=le(e.min,t);const i=[],s=Ts(t);let r=ny(t,n),o=r<0?Math.pow(10,Math.abs(r)):1;const a=Math.pow(10,r),l=s>r?Math.pow(10,s):0,c=Math.round((t-l)*o)/o,u=Math.floor((t-l)/a/10)*a*10;let d=Math.floor((c-u)/Math.pow(10,r)),h=le(e.min,Math.round((l+u+d*Math.pow(10,r))*o)/o);for(;h=10?d=d<15?15:20:d++,d>=20&&(r++,d=2,o=r>=0?1:o),h=Math.round((l+u+d*Math.pow(10,r))*o)/o;const f=le(e.max,h);return i.push({value:f,major:Dc(f),significand:d}),i}class ga extends pi{constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(t,n){const i=Wr.prototype.parse.apply(this,[t,n]);if(i===0){this._zero=!0;return}return Tt(i)&&i>0?i:null}determineDataLimits(){const{min:t,max:n}=this.getMinMax(!0);this.min=Tt(t)?Math.max(0,t):null,this.max=Tt(n)?Math.max(0,n):null,this.options.beginAtZero&&(this._zero=!0),this._zero&&this.min!==this._suggestedMin&&!Tt(this._userMin)&&(this.min=t===Hn(this.min,0)?Hn(this.min,-1):Hn(this.min,0)),this.handleTickRangeOptions()}handleTickRangeOptions(){const{minDefined:t,maxDefined:n}=this.getUserBounds();let i=this.min,s=this.max;const r=a=>i=t?i:a,o=a=>s=n?s:a;i===s&&(i<=0?(r(1),o(10)):(r(Hn(i,-1)),o(Hn(s,1)))),i<=0&&r(Hn(s,-1)),s<=0&&o(Hn(i,1)),this.min=i,this.max=s}buildTicks(){const t=this.options,n={min:this._userMin,max:this._userMax},i=iy(n,this);return t.bounds==="ticks"&&Zu(i,this,"value"),t.reverse?(i.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),i}getLabelForValue(t){return t===void 0?"0":Ys(t,this.chart.options.locale,this.options.ticks.format)}configure(){const t=this.min;super.configure(),this._startValue=Sn(t),this._valueRange=Sn(this.max)-Sn(t)}getPixelForValue(t){return(t===void 0||t===0)&&(t=this.min),t===null||isNaN(t)?NaN:this.getPixelForDecimal(t===this.min?0:(Sn(t)-this._startValue)/this._valueRange)}getValueForPixel(t){const n=this.getDecimalForPixel(t);return Math.pow(10,this._startValue+n*this._valueRange)}}z(ga,"id","logarithmic"),z(ga,"defaults",{ticks:{callback:eo.formatters.logarithmic,major:{enabled:!0}}});function pa(e){const t=e.ticks;if(t.display&&e.display){const n=Yt(t.backdropPadding);return Q(t.font&&t.font.size,kt.font.size)+n.height}return 0}function sy(e,t,n){return n=yt(n)?n:[n],{w:um(e,t.string,n),h:n.length*t.lineHeight}}function Oc(e,t,n,i,s){return e===i||e===s?{start:t-n/2,end:t+n/2}:es?{start:t-n,end:t}:{start:t,end:t+n}}function ry(e){const t={l:e.left+e._padding.left,r:e.right-e._padding.right,t:e.top+e._padding.top,b:e.bottom-e._padding.bottom},n=Object.assign({},t),i=[],s=[],r=e._pointLabels.length,o=e.options.pointLabels,a=o.centerPointLabels?ut/r:0;for(let l=0;lt.r&&(a=(i.end-t.r)/r,e.r=Math.max(e.r,t.r+a)),s.startt.b&&(l=(s.end-t.b)/o,e.b=Math.max(e.b,t.b+l))}function ay(e,t,n){const i=e.drawingArea,{extra:s,additionalAngle:r,padding:o,size:a}=n,l=e.getPointPosition(t,i+s+o,r),c=Math.round(Ba(Xt(l.angle+Rt))),u=hy(l.y,a.h,c),d=uy(c),h=dy(l.x,a.w,d);return{visible:!0,x:l.x,y:u,textAlign:d,left:h,top:u,right:h+a.w,bottom:u+a.h}}function ly(e,t){if(!t)return!0;const{left:n,top:i,right:s,bottom:r}=e;return!(an({x:n,y:i},t)||an({x:n,y:r},t)||an({x:s,y:i},t)||an({x:s,y:r},t))}function cy(e,t,n){const i=[],s=e._pointLabels.length,r=e.options,{centerPointLabels:o,display:a}=r.pointLabels,l={extra:pa(r)/2,additionalAngle:o?ut/s:0};let c;for(let u=0;u270||n<90)&&(e-=t),e}function fy(e,t,n){const{left:i,top:s,right:r,bottom:o}=n,{backdropColor:a}=t;if(!it(a)){const l=oi(t.borderRadius),c=Yt(t.backdropPadding);e.fillStyle=a;const u=i-c.left,d=s-c.top,h=r-i+c.width,f=o-s+c.height;Object.values(l).some(g=>g!==0)?(e.beginPath(),Cs(e,{x:u,y:d,w:h,h:f,radius:l}),e.fill()):e.fillRect(u,d,h,f)}}function gy(e,t){const{ctx:n,options:{pointLabels:i}}=e;for(let s=t-1;s>=0;s--){const r=e._pointLabelItems[s];if(!r.visible)continue;const o=i.setContext(e.getPointLabelContext(s));fy(n,o,r);const a=zt(o.font),{x:l,y:c,textAlign:u}=r;fi(n,e._pointLabels[s],l,c+a.lineHeight/2,a,{color:o.color,textAlign:u,textBaseline:"middle"})}}function Vd(e,t,n,i){const{ctx:s}=e;if(n)s.arc(e.xCenter,e.yCenter,t,0,wt);else{let r=e.getPointPosition(0,t);s.moveTo(r.x,r.y);for(let o=1;o{const s=pt(this.options.pointLabels.callback,[n,i],this);return s||s===0?s:""}).filter((n,i)=>this.chart.getDataVisibility(i))}fit(){const t=this.options;t.display&&t.pointLabels.display?ry(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(t,n,i,s){this.xCenter+=Math.floor((t-n)/2),this.yCenter+=Math.floor((i-s)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(t,n,i,s))}getIndexAngle(t){const n=wt/(this._pointLabels.length||1),i=this.options.startAngle||0;return Xt(t*n+De(i))}getDistanceFromCenterForValue(t){if(it(t))return NaN;const n=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-t)*n:(t-this.min)*n}getValueForDistanceFromCenter(t){if(it(t))return NaN;const n=t/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-n:this.min+n}getPointLabelContext(t){const n=this._pointLabels||[];if(t>=0&&t{if(d!==0||d===0&&this.min<0){l=this.getDistanceFromCenterForValue(u.value);const h=this.getContext(d),f=s.setContext(h),g=r.setContext(h);py(this,f,l,o,g)}}),i.display){for(t.save(),a=o-1;a>=0;a--){const u=i.setContext(this.getPointLabelContext(a)),{color:d,lineWidth:h}=u;!h||!d||(t.lineWidth=h,t.strokeStyle=d,t.setLineDash(u.borderDash),t.lineDashOffset=u.borderDashOffset,l=this.getDistanceFromCenterForValue(n.reverse?this.min:this.max),c=this.getPointPosition(a,l),t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(c.x,c.y),t.stroke())}t.restore()}}drawBorder(){}drawLabels(){const t=this.ctx,n=this.options,i=n.ticks;if(!i.display)return;const s=this.getIndexAngle(0);let r,o;t.save(),t.translate(this.xCenter,this.yCenter),t.rotate(s),t.textAlign="center",t.textBaseline="middle",this.ticks.forEach((a,l)=>{if(l===0&&this.min>=0&&!n.reverse)return;const c=i.setContext(this.getContext(l)),u=zt(c.font);if(r=this.getDistanceFromCenterForValue(this.ticks[l].value),c.showLabelBackdrop){t.font=u.string,o=t.measureText(a.label).width,t.fillStyle=c.backdropColor;const d=Yt(c.backdropPadding);t.fillRect(-o/2-d.left,-r-u.size/2-d.top,o+d.width,u.size+d.height)}fi(t,a.label,0,-r,u,{color:c.color,strokeColor:c.textStrokeColor,strokeWidth:c.textStrokeWidth})}),t.restore()}drawTitle(){}}z(cs,"id","radialLinear"),z(cs,"defaults",{display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,lineWidth:1,borderDash:[],borderDashOffset:0},grid:{circular:!1},startAngle:0,ticks:{showLabelBackdrop:!0,callback:eo.formatters.numeric},pointLabels:{backdropColor:void 0,backdropPadding:2,display:!0,font:{size:10},callback(t){return t},padding:5,centerPointLabels:!1}}),z(cs,"defaultRoutes",{"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"}),z(cs,"descriptors",{angleLines:{_fallback:"grid"}});const ao={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},ne=Object.keys(ao);function Lc(e,t){return e-t}function Rc(e,t){if(it(t))return null;const n=e._adapter,{parser:i,round:s,isoWeekday:r}=e._parseOpts;let o=t;return typeof i=="function"&&(o=i(o)),Tt(o)||(o=typeof i=="string"?n.parse(o,i):n.parse(o)),o===null?null:(s&&(o=s==="week"&&(Vi(r)||r===!0)?n.startOf(o,"isoWeek",r):n.startOf(o,s)),+o)}function Ic(e,t,n,i){const s=ne.length;for(let r=ne.indexOf(e);r=ne.indexOf(n);r--){const o=ne[r];if(ao[o].common&&e._adapter.diff(s,i,o)>=t-1)return o}return ne[n?ne.indexOf(n):0]}function _y(e){for(let t=ne.indexOf(e)+1,n=ne.length;t=t?n[i]:n[s];e[r]=!0}}function by(e,t,n,i){const s=e._adapter,r=+s.startOf(t[0].value,i),o=t[t.length-1].value;let a,l;for(a=r;a<=o;a=+s.add(a,1,i))l=n[a],l>=0&&(t[l].major=!0);return t}function zc(e,t,n){const i=[],s={},r=t.length;let o,a;for(o=0;o+t.value))}initOffsets(t=[]){let n=0,i=0,s,r;this.options.offset&&t.length&&(s=this.getDecimalForValue(t[0]),t.length===1?n=1-s:n=(this.getDecimalForValue(t[1])-s)/2,r=this.getDecimalForValue(t[t.length-1]),t.length===1?i=r:i=(r-this.getDecimalForValue(t[t.length-2]))/2);const o=t.length<3?.5:.25;n=Wt(n,0,o),i=Wt(i,0,o),this._offsets={start:n,end:i,factor:1/(n+1+i)}}_generate(){const t=this._adapter,n=this.min,i=this.max,s=this.options,r=s.time,o=r.unit||Ic(r.minUnit,n,i,this._getLabelCapacity(n)),a=Q(s.ticks.stepSize,1),l=o==="week"?r.isoWeekday:!1,c=Vi(l)||l===!0,u={};let d=n,h,f;if(c&&(d=+t.startOf(d,"isoWeek",l)),d=+t.startOf(d,c?"day":o),t.diff(i,n,o)>1e5*a)throw new Error(n+" and "+i+" are too far apart with stepSize of "+a+" "+o);const g=s.ticks.source==="data"&&this.getDataTimestamps();for(h=d,f=0;h+v)}getLabelForValue(t){const n=this._adapter,i=this.options.time;return i.tooltipFormat?n.format(t,i.tooltipFormat):n.format(t,i.displayFormats.datetime)}format(t,n){const s=this.options.time.displayFormats,r=this._unit,o=n||s[r];return this._adapter.format(t,o)}_tickFormatFunction(t,n,i,s){const r=this.options,o=r.ticks.callback;if(o)return pt(o,[t,n,i],this);const a=r.time.displayFormats,l=this._unit,c=this._majorUnit,u=l&&a[l],d=c&&a[c],h=i[n],f=c&&d&&h&&h.major;return this._adapter.format(t,s||(f?d:u))}generateTickLabels(t){let n,i,s;for(n=0,i=t.length;n0?a:1}getDataTimestamps(){let t=this._cache.data||[],n,i;if(t.length)return t;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(n=0,i=s.length;n=e[i].pos&&t<=e[s].pos&&({lo:i,hi:s}=on(e,"pos",t)),{pos:r,time:a}=e[i],{pos:o,time:l}=e[s]):(t>=e[i].time&&t<=e[s].time&&({lo:i,hi:s}=on(e,"time",t)),{time:r,pos:a}=e[i],{time:o,pos:l}=e[s]);const c=o-r;return c?a+(l-a)*(t-r)/c:a}class va extends Ds{constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this._getTimestampsForTable(),n=this._table=this.buildLookupTable(t);this._minPos=lr(n,this.min),this._tableRange=lr(n,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){const{min:n,max:i}=this,s=[],r=[];let o,a,l,c,u;for(o=0,a=t.length;o=n&&c<=i&&s.push(c);if(s.length<2)return[{time:n,pos:0},{time:i,pos:1}];for(o=0,a=s.length;os-r)}_getTimestampsForTable(){let t=this._cache.all||[];if(t.length)return t;const n=this.getDataTimestamps(),i=this.getLabelTimestamps();return n.length&&i.length?t=this.normalize(n.concat(i)):t=n.length?n:i,t=this._cache.all=t,t}getDecimalForValue(t){return(lr(this._table,t)-this._minPos)/this._tableRange}getValueForPixel(t){const n=this._offsets,i=this.getDecimalForPixel(t)/n.factor-n.end;return lr(this._table,i*this._tableRange+this._minPos,!0)}}z(va,"id","timeseries"),z(va,"defaults",Ds.defaults);var xy=Object.freeze({__proto__:null,CategoryScale:ha,LinearScale:fa,LogarithmicScale:ga,RadialLinearScale:cs,TimeScale:Ds,TimeSeriesScale:va});const yy=[P_,nx,Jx,xy];$e.register(...yy);/*! + * chartjs-plugin-datalabels v2.2.0 + * https://chartjs-plugin-datalabels.netlify.app + * (c) 2017-2022 chartjs-plugin-datalabels contributors + * Released under the MIT license + */var jc=(function(){if(typeof window<"u"){if(window.devicePixelRatio)return window.devicePixelRatio;var e=window.screen;if(e)return(e.deviceXDPI||1)/(e.logicalXDPI||1)}return 1})(),xs={toTextLines:function(e){var t=[],n;for(e=[].concat(e);e.length;)n=e.pop(),typeof n=="string"?t.unshift.apply(t,n.split(` +`)):Array.isArray(n)?e.push.apply(e,n):it(e)||t.unshift(""+n);return t},textSize:function(e,t,n){var i=[].concat(t),s=i.length,r=e.font,o=0,a;for(e.font=n.string,a=0;an.right&&(i|=Hd),tn.bottom&&(i|=qd),i}function Sy(e,t){for(var n=e.x0,i=e.y0,s=e.x1,r=e.y1,o=cr(n,i,t),a=cr(s,r,t),l,c,u;!(!(o|a)||o&a);)l=o||a,l&Yd?(c=n+(s-n)*(t.top-i)/(r-i),u=t.top):l&qd?(c=n+(s-n)*(t.bottom-i)/(r-i),u=t.bottom):l&Hd?(u=i+(r-i)*(t.right-n)/(s-n),c=t.right):l&Wd&&(u=i+(r-i)*(t.left-n)/(s-n),c=t.left),l===o?(n=c,i=u,o=cr(n,i,t)):(s=c,r=u,a=cr(s,r,t));return{x0:n,x1:s,y0:i,y1:r}}function ur(e,t){var n=t.anchor,i=e,s,r;return t.clamp&&(i=Sy(i,t.area)),n==="start"?(s=i.x0,r=i.y0):n==="end"?(s=i.x1,r=i.y1):(s=(i.x0+i.x1)/2,r=(i.y0+i.y1)/2),wy(s,r,e.vx,e.vy,t.align)}var dr={arc:function(e,t){var n=(e.startAngle+e.endAngle)/2,i=Math.cos(n),s=Math.sin(n),r=e.innerRadius,o=e.outerRadius;return ur({x0:e.x+i*r,y0:e.y+s*r,x1:e.x+i*o,y1:e.y+s*o,vx:i,vy:s},t)},point:function(e,t){var n=zo(e,t.origin),i=n.x*e.options.radius,s=n.y*e.options.radius;return ur({x0:e.x-i,y0:e.y-s,x1:e.x+i,y1:e.y+s,vx:n.x,vy:n.y},t)},bar:function(e,t){var n=zo(e,t.origin),i=e.x,s=e.y,r=0,o=0;return e.horizontal?(i=Math.min(e.x,e.base),r=Math.abs(e.base-e.x)):(s=Math.min(e.y,e.base),o=Math.abs(e.base-e.y)),ur({x0:i,y0:s+o,x1:i+r,y1:s,vx:n.x,vy:n.y},t)},fallback:function(e,t){var n=zo(e,t.origin);return ur({x0:e.x,y0:e.y,x1:e.x+(e.width||0),y1:e.y+(e.height||0),vx:n.x,vy:n.y},t)}},ln=xs.rasterize;function My(e){var t=e.borderWidth||0,n=e.padding,i=e.size.height,s=e.size.width,r=-s/2,o=-i/2;return{frame:{x:r-n.left-t,y:o-n.top-t,w:s+n.width+t*2,h:i+n.height+t*2},text:{x:r,y:o,w:s,h:i}}}function Py(e,t){var n=t.chart.getDatasetMeta(t.datasetIndex).vScale;if(!n)return null;if(n.xCenter!==void 0&&n.yCenter!==void 0)return{x:n.xCenter,y:n.yCenter};var i=n.getBasePixel();return e.horizontal?{x:i,y:null}:{x:null,y:i}}function Cy(e){return e instanceof ki?dr.arc:e instanceof _s?dr.point:e instanceof bs?dr.bar:dr.fallback}function Ay(e,t,n,i,s,r){var o=Math.PI/2;if(r){var a=Math.min(r,s/2,i/2),l=t+a,c=n+a,u=t+i-a,d=n+s-a;e.moveTo(t,c),li.x+i.w+n*2||e.y>i.y+i.h+n*2)},intersects:function(e){var t=this._points(),n=e._points(),i=[hr(t[0],t[1]),hr(t[0],t[3])],s,r,o;for(this._rotation!==e._rotation&&i.push(hr(n[0],n[1]),hr(n[0],n[3])),s=0;s=0;--n)for(s=e[n].$layout,i=n-1;i>=0&&s._visible;--i)r=e[i].$layout,r._visible&&s._box.intersects(r._box)&&t(s,r);return e}function Fy(e){var t,n,i,s,r,o,a;for(t=0,n=e.length;tl.getProps([c],!0)[c]}),r=i.geometry(),o=Kd(a,i.model(),r),s._box.update(o,r,i.rotation()));return Iy(e,function(l,c){var u=l._hidable,d=c._hidable;u&&d||d?c._visible=!1:u&&(l._visible=!1)})}var ys={prepare:function(e){var t=[],n,i,s,r,o;for(n=0,s=e.length;n=0;--n)if(i=e[n].$layout,i&&i._visible&&i._box.contains(t))return e[n];return null},draw:function(e,t){var n,i,s,r,o,a;for(n=0,i=t.length;n "),Yy=L('
                        ');function Si(e,t){tt(t,!0);let n=Gt(t,"height",3,240),i=j(null),s=null;function r(f,g){return typeof document>"u"?g:getComputedStyle(document.documentElement).getPropertyValue(f).trim()||g}function o(){return[r("--accent","#eaa45d"),r("--chart-2","#6aa3d8"),r("--chart-3","#7fb77e"),r("--chart-4","#d88c8c"),r("--chart-5","#b39ddb"),r("--chart-6","#e0a458")]}function a(){var b,x;if(!s)return;const f=o(),g=r("--text-primary","#1f2328"),v=r("--chart-grid","#dddddd"),p=(s.data.labels??[]).map((y,S)=>f[S%f.length]);if(t.spec.type==="pie")s.data.datasets[0].backgroundColor=p;else if(t.spec.type==="bar"&&s.data.datasets.length===2){const y=s.data.datasets[0],S=s.data.datasets[1];y.backgroundColor=p.map(D=>D+"80"),y.borderColor=p,y.borderWidth=2,S.backgroundColor=p}else t.spec.type==="line"&&s.data.datasets.forEach((y,S)=>{const D=y,T=f[S%f.length];D.borderColor=T,D.backgroundColor=T+"66",D.pointBackgroundColor=T,D.pointBorderColor=T});const _=s.options;if((x=(b=_.plugins)==null?void 0:b.legend)!=null&&x.labels&&(_.plugins.legend.labels.color=g),_.scales)for(const y of Object.values(_.scales))y&&(y.ticks={...y.ticks,color:g},y.grid={...y.grid,color:v},y.title&&(y.title.color=g));s.update()}be(()=>{if(!m(i))return;const f=m(i),g=Ie(()=>{var b;const v={...t.spec,plugins:[Hy]},p=(b=v.options)==null?void 0:b.onClick,_=(x,y,S)=>{var D;if(y.length>0&&t.onSliceClick){const T=y[0].index,A=(D=S.data.labels)==null?void 0:D[T];typeof A=="string"&&t.onSliceClick(A)}typeof p=="function"&&p(x,y,S)};return v.options={...v.options??{},onClick:_},new $e(f,v)});return s=g,Ie(()=>a()),()=>{s=null,g.destroy()}}),be(()=>{const f=t.spec;s&&(s.data.labels=f.data.labels??[],s.data.datasets.length!==f.data.datasets.length?(s.data.datasets=f.data.datasets,s.update("none")):f.data.datasets.forEach((g,v)=>{s.data.datasets[v]&&(s.data.datasets[v].data=g.data)}),a())}),be(()=>{ri.current,s&&a()});var l=Yy(),c=w(l);{var u=f=>{var g=qy(),v=w(g);W(()=>F(v,t.title)),P(f,g)};H(c,f=>{t.title&&f(u)})}var d=k(c,2),h=w(d);Bi(h,f=>O(i,f),()=>m(i)),W(()=>$r(d,`height: ${n()??""}px`)),P(e,l),et()}var Uy=L('
                        Calls
                        Input tokens
                        Output tokens
                        Total tokens
                        ');function Xy(e,t){tt(t,!0);const n=Y(()=>Object.values(t.stats).reduce((p,_)=>({calls:p.calls+_.num_times_called,input:p.input+_.input_tokens,output:p.output+_.output_tokens}),{calls:0,input:0,output:0}));var i=Uy(),s=w(i),r=k(w(s),2),o=w(r),a=k(s,2),l=k(w(a),2),c=w(l),u=k(a,2),d=k(w(u),2),h=w(d),f=k(u,2),g=k(w(f),2),v=w(g);W((p,_,b,x)=>{F(o,p),F(c,_),F(h,b),F(v,x)},[()=>wn(m(n).calls),()=>wn(m(n).input),()=>wn(m(n).output),()=>wn(m(n).input+m(n).output)]),P(e,i),et()}var Ky=L(""),Gy=L('');function Zy(e,t){tt(t,!1);const n=[{value:"calls",label:"Calls"},{value:"tokens",label:"Tokens"},{value:"duration_total",label:"Total duration"},{value:"duration_avg",label:"Avg duration"},{value:"errors",label:"Errors"}];Ra();var i=Gy(),s=k(w(i),2);Mt(s,5,()=>n,o=>o.value,(o,a)=>{var l=Ky(),c=w(l),u={};W(()=>{F(c,m(a).label),u!==(u=m(a).value)&&(l.value=(l.__value=m(a).value)??"")}),P(o,l)});var r;La(s),W(()=>{r!==(r=Lt.sortKey)&&(s.value=(s.__value=Lt.sortKey)??"",to(s,Lt.sortKey))}),K("change",s,o=>Lt.setSortKey(o.target.value)),P(e,i),et()}Et(["change"]);function Jy(e,t){tt(t,!0);const n=Y(()=>dv(Lt.stats,Lt.sortKey));Si(e,{title:"Duration (avg / max ms)",get spec(){return m(n)},height:360,get onSliceClick(){return t.onSliceClick}}),et()}var Qy=L(' ',1),$y=L('

                        Buffer is capped at 2000 records — long windows may show only the recent portion.

                        '),t0=L('

                        Tool Call Rate

                        ');function e0(e,t){tt(t,!0);let n=j(15),i=j(!1),s=j(mt(new Set));const r=Y(()=>Array.from(new Set(di.records.map(C=>C.tool))).sort()),o=Y(()=>m(i)?m(r).filter(C=>m(s).size===0||m(s).has(C)):[]);let a=j(mt(Math.floor(Date.now()/1e3)));be(()=>{const C=setInterval(()=>{O(a,Math.floor(Date.now()/1e3),!0)},5e3);return()=>clearInterval(C)});const l=Y(()=>fv(di.records,m(a),m(n),m(i),m(o)));function c(){O(s,new Set(m(r)),!0)}function u(){O(s,new Set,!0)}var d=t0(),h=w(d),f=k(w(h),2),g=w(f),v=k(w(g)),p=w(v);p.value=p.__value="15";var _=k(p);_.value=_.__value="30";var b=k(_);b.value=b.__value="60";var x=k(b);x.value=x.__value="360";var y;La(v);var S=k(g,2),D=w(S),T=k(S,2);{var A=C=>{var V=Qy(),q=st(V),N=k(q,2);K("click",q,c),K("click",N,u),P(C,V)};H(T,C=>{m(i)&&C(A)})}var E=k(h,2);Si(E,{get spec(){return m(l)},height:280});var R=k(E,2);{var I=C=>{var V=$y();P(C,V)};H(R,C=>{m(n)>=60&&C(I)})}W(C=>{y!==(y=C)&&(v.value=(v.__value=C)??"",to(v,C))},[()=>String(m(n))]),K("change",v,C=>O(n,Number(C.target.value),!0)),Vf(D,()=>m(i),C=>O(i,C)),P(e,d),et()}Et(["change","click"]);var n0=L('

                        No data for this tool.

                        '),i0=L("
                      • "),s0=L('

                        Recent errors

                          ',1),r0=L('
                        • '),o0=L(''),a0=L('
                          Calls
                          Errors
                          Avg
                          p95
                          Total
                          Max

                            ',1),l0=L('');function c0(e,t){tt(t,!0);const n=Y(()=>Lt.drillTool),i=Y(()=>m(n)?Lt.stats[m(n)]:void 0),s=Y(()=>m(n)?di.records.filter(g=>g.tool===m(n)):[]),r=Y(()=>m(s).map(g=>g.duration_ms)),o=Y(()=>Dp(m(r),95)),a=Y(()=>m(s).filter(g=>!g.success).slice(0,5)),l=Y(()=>m(s).slice(0,20)),c=Y(()=>m(i)&&m(i).num_times_called>0?(m(i).num_errors??0)/m(i).num_times_called*100:0),u=Y(()=>m(i)&&m(i).num_times_called>0?(m(i).total_duration_ms??0)/m(i).num_times_called:0);var d=Bt(),h=st(d);{var f=g=>{var v=l0(),p=w(v),_=w(p),b=w(_),x=k(_,2),y=k(p,2);{var S=T=>{var A=n0();P(T,A)},D=T=>{var A=a0(),E=st(A),R=k(w(E),2),I=w(R),C=k(R,4),V=w(C),q=k(C,4),N=w(q),X=k(q,4),G=w(X),B=k(X,4),U=w(B),ct=k(B,4),bt=w(ct),dt=k(E,2),vt=w(dt),Ot=k(dt,2);{var Ke=ft=>{var It=s0(),ke=k(st(It),2);Mt(ke,21,()=>m(a),fn=>fn.seq,(fn,vi)=>{var Nn=i0(),Ki=w(Nn);W(mi=>F(Ki,`${mi??""} — ${m(vi).error_message??""}`),[()=>new Date(m(vi).started_at*1e3).toISOString().slice(11,19)]),P(fn,Nn)}),P(ft,It)};H(Ot,ft=>{m(a).length>0&&ft(Ke)})}var oe=k(Ot,2),co=w(oe),Xs=k(oe,2);Mt(Xs,21,()=>m(l),ft=>ft.seq,(ft,It)=>{var ke=r0(),fn=w(ke),vi=w(fn),Nn=k(fn,2),Ki=w(Nn),mi=k(Nn,2),eh=w(mi);W((nh,ih)=>{F(vi,nh),F(Ki,ih),At(mi,1,Lf(m(It).success?"ok":"err"),"svelte-1kpn8jr"),F(eh,m(It).success?"ok":"ERR")},[()=>new Date(m(It).started_at*1e3).toISOString().slice(11,19),()=>sn(m(It).duration_ms)]),P(ft,ke)});var uo=k(Xs,2);{var St=ft=>{var It=o0();K("click",It,()=>{var ke;return(ke=t.onOpenInTimeline)==null?void 0:ke.call(t,m(n))}),P(ft,It)};H(uo,ft=>{t.onOpenInTimeline&&ft(St)})}W((ft,It,ke,fn,vi,Nn,Ki,mi)=>{F(I,ft),F(V,`${It??""} (${ke??""} %)`),F(N,fn),F(G,vi),F(U,Nn),F(bt,Ki),F(vt,`p95 based on last ${m(s).length??""} calls in the live buffer.`),F(co,`Last ${mi??""} calls`)},[()=>wn(m(i).num_times_called),()=>wn(m(i).num_errors??0),()=>m(c).toFixed(2),()=>sn(m(u)),()=>sn(m(o)),()=>sn(m(i).total_duration_ms??0),()=>sn(m(i).max_duration_ms??0),()=>Math.min(20,m(l).length)]),P(T,A)};H(y,T=>{m(i)?T(D,-1):T(S)})}W(()=>F(b,m(n))),K("click",x,()=>Lt.setDrillTool(null)),P(g,v)};H(h,g=>{m(n)&&g(f)})}P(e,d),et()}Et(["click"]);var u0=L('
                            ',1),d0=L('
                            No tool stats collected yet.
                            '),h0=L('
                            ',1);function f0(e,t){tt(t,!0);const n=Y(()=>Object.keys(Lt.stats).length>0),i=f=>Lt.setDrillTool(f);dn(()=>{Lt.refresh()});var s=h0(),r=st(s),o=w(r);se(o,{onclick:()=>Lt.refresh(),children:(f,g)=>{var v=Ft("Refresh Stats");P(f,v)},$$slots:{default:!0}});var a=k(o,2);se(a,{onclick:()=>Lt.clear(),children:(f,g)=>{var v=Ft("Clear Stats");P(f,v)},$$slots:{default:!0}});var l=k(a,4);Zy(l,{});var c=k(r,2);{var u=f=>{var g=u0(),v=st(g);Xy(v,{get stats(){return Lt.stats}});var p=k(v,2),_=w(p),b=k(p,2),x=w(b);{let C=Y(()=>wo(Lt.stats,"num_times_called"));Si(x,{title:"Tool Calls",height:360,get spec(){return m(C)},onSliceClick:i})}var y=k(x,2);{let C=Y(()=>wo(Lt.stats,"input_tokens"));Si(y,{title:"Input Tokens",height:360,get spec(){return m(C)},onSliceClick:i})}var S=k(y,2);{let C=Y(()=>wo(Lt.stats,"output_tokens"));Si(S,{title:"Output Tokens",height:360,get spec(){return m(C)},onSliceClick:i})}var D=k(b,2),T=w(D);{let C=Y(()=>uv(Lt.stats,Lt.sortKey));Si(T,{title:"Token Usage (by tool)",height:460,get spec(){return m(C)},onSliceClick:i})}var A=k(D,2),E=w(A);Jy(E,{onSliceClick:i});var R=k(A,2),I=w(R);e0(I,{}),W(()=>F(_,`Token estimator: ${Lt.estimator??""}`)),P(f,g)},d=f=>{var g=d0();P(f,g)};H(c,f=>{m(n)?f(u):f(d,-1)})}var h=k(c,2);c0(h,{get onOpenInTimeline(){return t.onOpenInTimeline}}),P(e,s),et()}var g0=["forEach","isDisjointFrom","isSubsetOf","isSupersetOf"],p0=["difference","intersection","symmetricDifference","union"],Bc=!1,Li,Ce,bn,Ur,Yi,Zd,Jd;const Xr=class Xr extends Set{constructor(n){super();J(this,Yi);J(this,Li,new Map);J(this,Ce,j(0));J(this,bn,j(0));J(this,Ur,un||-1);if(n){for(var i of n)super.add(i);M(this,bn).v=super.size}Bc||lt(this,Yi,Jd).call(this)}has(n){var i=super.has(n),s=M(this,Li),r=s.get(n);if(r===void 0){if(!i)return m(M(this,Ce)),!1;r=lt(this,Yi,Zd).call(this,!0),s.set(n,r)}return m(r),i}add(n){return super.has(n)||(super.add(n),O(M(this,bn),super.size),En(M(this,Ce))),this}delete(n){var i=super.delete(n),s=M(this,Li),r=s.get(n);return r!==void 0&&(s.delete(n),O(r,!1)),i&&(O(M(this,bn),super.size),En(M(this,Ce))),i}clear(){if(super.size!==0){super.clear();var n=M(this,Li);for(var i of n.values())O(i,!1);n.clear(),O(M(this,bn),0),En(M(this,Ce))}}keys(){return this.values()}values(){return m(M(this,Ce)),super.values()}entries(){return m(M(this,Ce)),super.entries()}[Symbol.iterator](){return this.keys()}get size(){return m(M(this,bn))}};Li=new WeakMap,Ce=new WeakMap,bn=new WeakMap,Ur=new WeakMap,Yi=new WeakSet,Zd=function(n){return un===M(this,Ur)?j(n):Rn(n)},Jd=function(){Bc=!0;var n=Xr.prototype,i=Set.prototype;for(const s of g0)n[s]=function(...r){return m(M(this,Ce)),i[s].apply(this,r)};for(const s of p0)n[s]=function(...r){m(M(this,Ce));var o=i[s].apply(this,r);return new Xr(o)}};let _a=Xr;function v0(){const e=mt({}),t=mt({}),n=new _a;let i=j(null);const s=mt({}),r=mt({});let o=j(""),a=j(mt([])),l=j(!1),c=j(null),u=j("symbols"),d=0,h=j(mt([])),f=j(!1),g=j(null),v=j(!1),p=j(null);return{get dir_children(){return e},get dir_errors(){return t},get expanded(){return n},get selected_path(){return m(i)},get file_symbols(){return s},get file_symbol_errors(){return r},get search_query(){return m(o)},get search_results(){return m(a)},get search_loading(){return m(l)},get search_error(){return m(c)},get middle_pane(){return m(u)},get diag_files(){return m(h)},get diag_loading(){return m(f)},get diag_error(){return m(g)},get diag_truncated(){return m(v)},get diag_last_refresh_at(){return m(p)},setMiddlePane(_){O(u,_,!0)},async loadDir(_,b=!1){if(!(!b&&e[_])){delete t[_];try{const x=await Mg(_);e[_]=x.entries}catch(x){t[_]=x instanceof Error?x.message:String(x)}}},toggleExpand(_){n.has(_)?n.delete(_):(n.add(_),this.loadDir(_))},selectPath(_,b={}){O(i,_,!0),b.switchMiddleTo&&O(u,b.switchMiddleTo,!0),_&&this.loadFileSymbols(_)},async loadFileSymbols(_,b=!1){if(!(!b&&s[_])){delete r[_];try{const x=await Pg(_);s[_]=x.symbols}catch(x){r[_]=x instanceof Error?x.message:String(x)}}},async search(_){if(O(o,_,!0),_.trim().length<2){O(a,[],!0),O(l,!1),O(c,null);return}const b=++d;O(l,!0),O(c,null);try{const x=await Cg(_,50);b===d&&O(a,x.matches,!0)}catch(x){b===d&&O(c,x instanceof Error?x.message:String(x),!0)}finally{b===d&&O(l,!1)}},async refreshDiagnostics(_=1e3){O(f,!0),O(g,null);try{const b=await Ag(_);O(h,b.files,!0),O(v,b.truncated,!0),O(p,Date.now(),!0)}catch(b){O(g,b instanceof Error?b.message:String(b),!0)}finally{O(f,!1)}}}}const $=v0();var m0=L('
                          • '),_0=L('
                          • '),b0=L(''),x0=L(' ',1),y0=L(''),w0=L("
                          • "),k0=L('
                            ');function S0(e,t){tt(t,!0);const n=(r,o=ws,a=ws)=>{const l=Y(()=>$.dir_children[o()]),c=Y(()=>$.dir_errors[o()]);var u=k0();let d;var h=w(u);{var f=p=>{var _=m0(),b=w(_),x=k(b,2),y=w(x);W(()=>{_t(b,"title",m(c)),F(y,m(c))}),P(p,_)},g=p=>{var _=_0();P(p,_)},v=p=>{var _=Bt(),b=st(_);Mt(b,17,()=>m(l),x=>x.name,(x,y)=>{const S=Y(()=>s(o(),m(y).name));var D=w0(),T=w(D);{var A=R=>{var I=x0(),C=st(I),V=w(C),q=w(V),N=k(V,2),X=w(N),G=k(N,2);{var B=dt=>{var vt=b0();W(()=>_t(vt,"title",$.dir_errors[m(S)])),P(dt,vt)};H(G,dt=>{$.dir_errors[m(S)]!==void 0&&dt(B)})}var U=k(C,2);{var ct=dt=>{n(dt,()=>m(S),()=>a()+1)},bt=Y(()=>$.expanded.has(m(S)));H(U,dt=>{m(bt)&&dt(ct)})}W((dt,vt)=>{_t(C,"aria-expanded",dt),F(q,vt),F(X,m(y).name)},[()=>$.expanded.has(m(S)),()=>$.expanded.has(m(S))?"▾":"▸"]),K("click",C,()=>$.toggleExpand(m(S))),P(R,I)},E=R=>{var I=y0();let C;var V=k(w(I),2),q=w(V);W(()=>{C=At(I,1,"row file svelte-1pcafu8",null,C,{selected:$.selected_path===m(S)}),F(q,m(y).name)}),K("click",I,()=>$.selectPath(m(S))),P(R,I)};H(T,R=>{m(y).kind==="dir"?R(A):R(E,-1)})}P(x,D)}),P(p,_)};H(h,p=>{m(c)!==void 0?p(f):m(l)?p(v,-1):p(g,1)})}W(()=>d=$r(u,"",d,{"--depth":a()})),P(r,u)};let i=Gt(t,"rootPath",3,".");be(()=>{$.loadDir(i())});function s(r,o){return r==="."||r===""?o:`${r}/${o}`}n(e,i,()=>0),et()}Et(["click"]);const Qd=(e,t=ws,n=ws)=>{var i=M0(),s=st(i);let r;var o=w(s),a=w(o),l=k(o,2),c=w(l),u=k(l,2),d=w(u),h=k(s,2);{var f=g=>{var v=Bt(),p=st(v);Mt(p,17,()=>t().children,_=>_.name+":"+_.range.start.line,(_,b)=>{Qd(_,()=>m(b),()=>n()+1)}),P(g,v)};H(h,g=>{t().children&&t().children.length>0&&g(f)})}W(()=>{r=$r(s,"",r,{"--depth":n()}),F(a,t().kind),F(c,t().name),F(d,`${t().range.start.line+1}:${t().range.start.character+1}`)}),P(e,i)};var M0=L('
                          • ',1),P0=L('

                            Select a file from the tree.

                            '),C0=L('
                            Failed to load symbols.
                            '),A0=L('

                            Loading…

                            '),T0=L('

                            No symbols.

                            '),D0=L('
                              '),E0=L('
                              ');function O0(e,t){tt(t,!0);const n=Y(()=>$.selected_path),i=Y(()=>m(n)?$.file_symbols[m(n)]:void 0),s=Y(()=>m(n)?$.file_symbol_errors[m(n)]:void 0);function r(){m(n)&&$.loadFileSymbols(m(n),!0)}var o=E0(),a=w(o);{var l=f=>{var g=P0();P(f,g)},c=f=>{var g=C0(),v=k(w(g),2),p=w(v),_=k(v,2);W(()=>F(p,m(s))),K("click",_,r),P(f,g)},u=f=>{var g=A0();P(f,g)},d=f=>{var g=T0();P(f,g)},h=f=>{var g=D0();Mt(g,21,()=>m(i),v=>v.name+":"+v.range.start.line,(v,p)=>{Qd(v,()=>m(p),()=>0)}),P(f,g)};H(a,f=>{m(n)?m(s)!==void 0?f(c,1):m(i)===void 0?f(u,2):m(i).length===0?f(d,3):f(h,-1):f(l)})}P(e,o),et()}Et(["click"]);var L0=L('
                              Search failed.
                              '),R0=L('
                              Searching…
                              '),I0=L('
                              No matches.
                              '),F0=L('
                            • '),z0=L('
                                '),j0=L('
                                ');function N0(e,t){tt(t,!0);let n=j(mt($.search_query)),i=null;function s(p){O(n,p.target.value,!0),i&&clearTimeout(i),i=setTimeout(()=>{$.search(m(n))},250)}function r(p){$.selectPath(p,{switchMiddleTo:"symbols"})}var o=j0(),a=w(o),l=k(a,2);{var c=p=>{var _=L0(),b=k(w(_),2),x=w(b);W(()=>F(x,$.search_error)),P(p,_)};H(l,p=>{$.search_error&&p(c)})}var u=k(l,2);{var d=p=>{var _=R0();P(p,_)},h=p=>{var _=I0();P(p,_)},f=Y(()=>m(n).trim().length>=2&&$.search_results.length===0&&!$.search_error);H(u,p=>{$.search_loading?p(d):m(f)&&p(h,1)})}var g=k(u,2);{var v=p=>{var _=z0();Mt(_,21,()=>$.search_results,b=>b.path+":"+b.name+":"+b.range.start.line,(b,x)=>{var y=F0(),S=w(y),D=w(S),T=w(D),A=k(D,2),E=w(A),R=k(A,2),I=w(R);W(()=>{F(T,m(x).kind),F(E,m(x).name),F(I,m(x).path)}),K("click",S,()=>r(m(x).path)),P(b,y)}),P(p,_)};H(g,p=>{$.search_results.length>0&&p(v)})}W(()=>Nf(a,m(n))),K("input",a,s),P(e,o),et()}Et(["input","click"]);var B0=L('
                                ⚠ Computing diagnostics — this can take a while and temporarily slows other LSP-backed tools.
                                '),V0=L('
                                Diagnostics failed.
                                '),W0=L('
                                '),H0=L(`

                                Click Refresh to compute diagnostics. (Results are not auto-refreshed because diagnostics is + slow.)

                                `),q0=L(' '),Y0=L('
                              • '),U0=L('
                                  '),X0=L('

                                  Diagnostics

                                  ');function K0(e,t){tt(t,!1);function n(){$.diag_loading||$.refreshDiagnostics(1e3)}Ra();var i=X0(),s=w(i),r=k(w(s),2),o=w(r),a=k(s,2);{var l=p=>{var _=B0();P(p,_)};H(a,p=>{$.diag_loading&&p(l)})}var c=k(a,2);{var u=p=>{var _=V0(),b=k(w(_),2),x=w(b);W(()=>F(x,$.diag_error)),P(p,_)};H(c,p=>{$.diag_error&&p(u)})}var d=k(c,2);{var h=p=>{var _=W0(),b=w(_);W(()=>F(b,`Showing first ${$.diag_files.length??""} files; project has more.`)),P(p,_)};H(d,p=>{$.diag_truncated&&!$.diag_loading&&p(h)})}var f=k(d,2);{var g=p=>{var _=H0();P(p,_)};H(f,p=>{$.diag_files.length===0&&!$.diag_loading&&!$.diag_error&&p(g)})}var v=k(f,2);Mt(v,1,()=>$.diag_files,p=>p.path,(p,_)=>{var b=U0(),x=w(b),y=w(x),S=w(y),D=k(y,2),T=w(D),A=k(x,2);Mt(A,5,()=>m(_).diagnostics,zu,(E,R)=>{var I=Y0(),C=w(I),V=w(C),q=k(C,2),N=w(q),X=k(q,2),G=w(X),B=k(X,2);{var U=ct=>{var bt=q0(),dt=w(bt);W(()=>F(dt,`[${m(R).source??""}]`)),P(ct,bt)};H(B,ct=>{m(R).source&&ct(U)})}W(()=>{At(I,1,`sev-${m(R).severity}`,"svelte-umravk"),F(V,`${m(R).line+1}:${m(R).column+1}`),F(N,m(R).severity),F(G,m(R).message)}),P(E,I)}),W(()=>{F(S,m(_).path),F(T,m(_).diagnostics.length)}),P(p,b)}),W(()=>{r.disabled=$.diag_loading,F(o,$.diag_loading?"Computing…":"↻ Refresh")}),K("click",r,n),P(e,i),et()}Et(["click"]);var G0=L('
                                  ');function Z0(e,t){tt(t,!1),Ra();var n=G0(),i=w(n),s=w(i);S0(s,{});var r=k(i,2),o=w(r),a=w(o);let l;var c=k(a,2);let u;var d=k(o,2);{var h=p=>{O0(p,{})},f=p=>{N0(p,{})};H(d,p=>{$.middle_pane==="symbols"?p(h):p(f,-1)})}var g=k(r,2),v=w(g);K0(v,{}),W(()=>{l=At(a,1,"svelte-z2f184",null,l,{active:$.middle_pane==="symbols"}),u=At(c,1,"svelte-z2f184",null,u,{active:$.middle_pane==="search"})}),K("click",a,()=>$.setMiddlePane("symbols")),K("click",c,()=>$.setMiddlePane("search")),P(e,n),et()}Et(["click"]);function J0(){let e=j(null);return{get active(){return m(e)},open(t){O(e,t,!0)},close(){O(e,null)}}}const Me=J0();var Q0=L("

                                  "),$0=L(''),t1=L('');function Us(e,t){tt(t,!0);let n=Gt(t,"open",3,!1),i=Gt(t,"title",3,""),s=Gt(t,"error",3,""),r=j(null),o=null;be(()=>{n()&&m(r)&&(o=document.activeElement,m(r).focus())}),Vu(()=>o==null?void 0:o.focus());function a(d){if(d.key==="Escape"){t.onclose();return}if(d.key!=="Tab"||!n()||!m(r))return;const h=m(r).querySelectorAll('a[href], button:not([disabled]), input, textarea, select, [tabindex]:not([tabindex="-1"])');if(h.length===0)return;const f=h[0],g=h[h.length-1],v=document.activeElement;d.shiftKey&&v===f?(d.preventDefault(),g.focus()):!d.shiftKey&&v===g&&(d.preventDefault(),f.focus())}var l=Bt();Ea("keydown",Zo,a);var c=st(l);{var u=d=>{var h=t1(),f=w(h),g=w(f),v=k(g,2);{var p=y=>{var S=Q0(),D=w(S);W(()=>F(D,i())),P(y,S)};H(v,y=>{i()&&y(p)})}var _=k(v,2);{var b=y=>{var S=$0(),D=w(S);W(()=>F(D,s())),P(y,S)};H(_,y=>{s()&&y(b)})}var x=k(_,2);Ws(x,()=>t.children),Bi(f,y=>O(r,y),()=>m(r)),K("click",h,function(...y){var S;(S=t.onclose)==null||S.apply(this,y)}),K("keydown",h,a),K("click",f,y=>y.stopPropagation()),K("keydown",f,y=>{a(y),y.stopPropagation()}),K("click",g,function(...y){var S;(S=t.onclose)==null||S.apply(this,y)}),P(d,h)};H(c,d=>{n()&&d(u)})}P(e,l),et()}Et(["click","keydown"]);function qi(){let e=j(!1),t=j("");return{get busy(){return m(e)},get error(){return m(t)},clearError(){O(t,"")},async run(n,i){O(e,!0),O(t,"");const s=await n();return O(e,!1),s.ok?(i(),s):(O(t,s.message??"",!0),s)}}}var e1=L(' ',1);function lo(e,t){tt(t,!0);let n=Gt(t,"title",3,""),i=Gt(t,"confirmLabel",3,"OK"),s=Gt(t,"variant",3,"primary");const r=qi();Us(e,{open:!0,get title(){return n()},get error(){return r.error},get onclose(){return t.onclose},children:(o,a)=>{var l=e1(),c=st(l),u=w(c);Ws(u,()=>t.children);var d=k(c,2),h=w(d);se(h,{variant:"secondary",get onclick(){return t.onclose},children:(g,v)=>{var p=Ft("Cancel");P(g,p)},$$slots:{default:!0}});var f=k(h,2);se(f,{get variant(){return s()},get disabled(){return r.busy},onclick:()=>r.run(t.onconfirm,t.onclose),children:(g,v)=>{var p=Bt(),_=st(p);{var b=y=>{Hs(y)},x=y=>{var S=Ft();W(()=>F(S,i())),P(y,S)};H(_,y=>{r.busy?y(b):y(x,-1)})}P(g,p)},$$slots:{default:!0}}),P(o,l)},$$slots:{default:!0}}),et()}function n1(e,t){tt(t,!0);async function n(){const i=await Oe(()=>ug());return i.ok&&setTimeout(()=>window.close(),1e3),i}lo(e,{title:"Shutdown Server",confirmLabel:"Shutdown",variant:"danger",onconfirm:n,get onclose(){return t.onclose},children:(i,s)=>{var r=Ft("Shut down the Serena server?");P(i,r)},$$slots:{default:!0}}),et()}function i1(e,t){tt(t,!0);function n(){Ve.clearCancelError(),t.onclose()}lo(e,{onconfirm:()=>Ve.cancel(t.execution),onclose:n,children:(i,s)=>{var r=Ft(`Are you sure? The execution will continue running until timeout, it will simply no longer be in + the queue. Abandoning a running execution is only advised as a measure for unblocking Serena.`);P(i,r)},$$slots:{default:!0}}),et()}var s1=L('
                                • '),r1=L('
                                    '),o1=L('
                                    No options available
                                    '),a1=L('
                                    ');function l1(e,t){tt(t,!0);let n=Gt(t,"value",3,""),i=Gt(t,"placeholder",3,"Type to filter…"),s=j(mt(n())),r=j(!1),o=j(null);const a=Y(()=>t.options.filter(v=>v.toLowerCase().includes(m(s).toLowerCase())));function l(v){O(s,v,!0),O(r,!1),t.onselect(v)}function c(v){var _;const p=v.relatedTarget;p&&((_=m(o))!=null&&_.contains(p))||O(r,!1)}function u(v,p){(v.key==="Enter"||v.key===" ")&&(v.preventDefault(),l(p))}var d=a1(),h=w(d),f=k(h,4);{var g=v=>{var p=Bt(),_=st(p);{var b=y=>{var S=r1();Mt(S,20,()=>m(a),D=>D,(D,T)=>{var A=s1(),E=w(A);W(()=>{_t(A,"aria-selected",T===m(s)),F(E,T)}),K("click",A,()=>l(T)),K("keydown",A,R=>u(R,T)),P(D,A)}),P(y,S)},x=y=>{var S=o1();P(y,S)};H(_,y=>{m(a).length?y(b):y(x,-1)})}P(v,p)};H(f,v=>{m(r)&&v(g)})}Bi(d,v=>O(o,v),()=>m(o)),W(()=>_t(h,"placeholder",i())),K("focusout",d,c),Ea("focus",h,()=>O(r,!0)),K("input",h,()=>O(r,!0)),Ni(h,()=>m(s),v=>O(s,v)),P(e,d),et()}Et(["focusout","input","click","keydown"]);var c1=L(` `,1);function u1(e,t){tt(t,!0);let n=j(mt([])),i=j("");const s=qi();dn(()=>{(async()=>O(n,(await dg()).languages,!0))()});function r(){m(i)&&s.run(()=>Oe(()=>hg(m(i))),t.onclose)}Us(e,{open:!0,title:"Add Language",get error(){return s.error},get onclose(){return t.onclose},children:(o,a)=>{var l=c1(),c=st(l),u=k(w(c)),d=w(u),h=k(c,4);l1(h,{get options(){return m(n)},get value(){return m(i)},onselect:p=>O(i,p,!0)});var f=k(h,2),g=w(f);se(g,{variant:"secondary",get onclick(){return t.onclose},children:(p,_)=>{var b=Ft("Cancel");P(p,b)},$$slots:{default:!0}});var v=k(g,2);{let p=Y(()=>s.busy||!m(i));se(v,{get disabled(){return m(p)},onclick:r,children:(_,b)=>{var x=Bt(),y=st(x);{var S=T=>{Hs(T)},D=T=>{var A=Ft("Add Language");P(T,A)};H(y,T=>{s.busy?T(S):T(D,-1)})}P(_,x)},$$slots:{default:!0}})}W(()=>F(d,t.projectName)),P(o,l)},$$slots:{default:!0}}),et()}var d1=L("Remove language from configuration?",1);function h1(e,t){tt(t,!0),lo(e,{onconfirm:()=>Oe(()=>fg(t.language)),get onclose(){return t.onclose},children:(n,i)=>{var s=d1(),r=k(st(s)),o=w(r);W(()=>F(o,t.language)),P(n,s)},$$slots:{default:!0}}),et()}function $d(e){return/^[A-Za-z0-9_]+(\/[A-Za-z0-9_]+)*$/.test(e)}function th(e){return!e||window.confirm("You have unsaved changes. Discard them?")}var f1=L(' ',1),g1=L(' ',1),p1=L(' ',1);function v1(e,t){tt(t,!0);let n=j(mt(Ie(()=>t.name))),i=j(""),s=j(""),r=j(!1),o=j(""),a=j(!1),l=j(mt(Ie(()=>t.name)));const c=Y(()=>m(i)!==m(s)),u=qi(),d=qi();async function h(){var b;const p=await Oe(()=>gg(t.name));if(!p.ok){O(o,p.message??"Failed to load memory.",!0);return}const _=((b=p.data)==null?void 0:b.content)??"";O(i,_,!0),O(s,_,!0),O(r,!0)}dn(()=>{h()});function f(){th(m(c))&&t.onclose()}function g(){u.run(()=>Oe(()=>Hu(m(n),m(i))),t.onclose)}function v(){if(!$d(m(l))||m(l)===m(n)){O(a,!1);return}d.run(()=>Oe(()=>vg(m(n),m(l))),()=>{O(n,m(l),!0),O(a,!1)})}{let p=Y(()=>m(o)||u.error||d.error);Us(e,{open:!0,get error(){return m(p)},onclose:f,children:(_,b)=>{var x=p1(),y=st(x),S=k(w(y));{var D=C=>{var V=f1(),q=st(V),N=k(q,2);W(()=>N.disabled=d.busy),K("keydown",q,X=>X.key==="Enter"&&v()),Ni(q,()=>m(l),X=>O(l,X)),K("click",N,v),P(C,V)},T=C=>{var V=g1(),q=st(V),N=w(q),X=k(q,2);W(()=>F(N,m(n))),K("click",X,()=>{O(a,!0),O(l,m(n),!0)}),P(C,V)};H(S,C=>{m(a)?C(D):C(T,-1)})}var A=k(y,2),E=k(A,2),R=w(E);se(R,{variant:"secondary",onclick:f,children:(C,V)=>{var q=Ft("Cancel");P(C,q)},$$slots:{default:!0}});var I=k(R,2);{let C=Y(()=>!m(r)||u.busy);se(I,{get disabled(){return m(C)},onclick:g,children:(V,q)=>{var N=Ft("Save");P(V,N)},$$slots:{default:!0}})}Ni(A,()=>m(i),C=>O(i,C)),P(_,x)},$$slots:{default:!0}})}et()}Et(["keydown","click"]);var m1=L(` `,1);function _1(e,t){tt(t,!0);let n=j("");const i=Y(()=>$d(m(n))),s=qi();function r(){m(i)&&s.run(()=>Oe(()=>Hu(m(n),"")),()=>t.oncreated(m(n)))}Us(e,{open:!0,title:"Create Memory",get error(){return s.error},get onclose(){return t.onclose},children:(o,a)=>{var l=m1(),c=st(l),u=k(w(c)),d=w(u),h=k(c,4),f=k(h,2),g=w(f);se(g,{variant:"secondary",get onclick(){return t.onclose},children:(p,_)=>{var b=Ft("Cancel");P(p,b)},$$slots:{default:!0}});var v=k(g,2);{let p=Y(()=>!m(i)||s.busy);se(v,{get disabled(){return m(p)},onclick:r,children:(_,b)=>{var x=Bt(),y=st(x);{var S=T=>{Hs(T)},D=T=>{var A=Ft("Create");P(T,A)};H(y,T=>{s.busy?T(S):T(D,-1)})}P(_,x)},$$slots:{default:!0}})}W(()=>F(d,t.projectName)),Ni(h,()=>m(n),p=>O(n,p)),P(o,l)},$$slots:{default:!0}}),et()}var b1=L("Delete memory ?",1);function x1(e,t){tt(t,!0),lo(e,{variant:"danger",onconfirm:()=>Oe(()=>pg(t.name)),get onclose(){return t.onclose},children:(n,i)=>{var s=b1(),r=k(st(s)),o=w(r);W(()=>F(o,t.name)),P(n,s)},$$slots:{default:!0}}),et()}var y1=L(' ',1);function w1(e,t){tt(t,!0);let n=j(""),i=j(""),s=j(!1),r=j("");const o=Y(()=>m(n)!==m(i)),a=qi();async function l(){var f;const d=await Oe(()=>mg());if(!d.ok){O(r,d.message??"Failed to load configuration.",!0);return}const h=((f=d.data)==null?void 0:f.content)??"";O(n,h,!0),O(i,h,!0),O(s,!0)}dn(()=>{l()});function c(){th(m(o))&&t.onclose()}function u(){a.run(()=>Oe(()=>_g(m(n))),t.onclose)}{let d=Y(()=>m(r)||a.error);Us(e,{open:!0,title:"Global Serena Configuration",get error(){return m(d)},onclose:c,children:(h,f)=>{var g=y1(),v=k(st(g),2),p=k(v,2),_=w(p);se(_,{variant:"secondary",onclick:c,children:(x,y)=>{var S=Ft("Cancel");P(x,S)},$$slots:{default:!0}});var b=k(_,2);{let x=Y(()=>!m(s)||a.busy);se(b,{get disabled(){return m(x)},onclick:u,children:(y,S)=>{var D=Ft("Save");P(y,D)},$$slots:{default:!0}})}Ni(v,()=>m(n),x=>O(n,x)),P(h,g)},$$slots:{default:!0}})}et()}function k1(e,t){tt(t,!0);const n=Y(()=>{var l,c;return String(((c=(l=Lr.data)==null?void 0:l.active_project)==null?void 0:c.name)??"")}),i=Y(()=>Me.active),s=()=>Me.close();var r=Bt(),o=st(r);{var a=l=>{var c=Bt(),u=st(c);{var d=x=>{n1(x,{onclose:s})},h=x=>{i1(x,{get execution(){return m(i).execution},onclose:s})},f=x=>{u1(x,{get projectName(){return m(n)},onclose:s})},g=x=>{h1(x,{get language(){return m(i).language},onclose:s})},v=x=>{var y=Bt(),S=st(y);Af(S,()=>m(i).name,D=>{v1(D,{get name(){return m(i).name},onclose:s})}),P(x,y)},p=x=>{_1(x,{get projectName(){return m(n)},onclose:s,oncreated:y=>Me.open({kind:"editMemory",name:y})})},_=x=>{x1(x,{get name(){return m(i).name},onclose:s})},b=x=>{w1(x,{onclose:s})};H(u,x=>{m(i).kind==="shutdown"?x(d):m(i).kind==="cancelExecution"?x(h,1):m(i).kind==="addLanguage"?x(f,2):m(i).kind==="removeLanguage"?x(g,3):m(i).kind==="editMemory"?x(v,4):m(i).kind==="createMemory"?x(p,5):m(i).kind==="deleteMemory"?x(_,6):m(i).kind==="editSerenaConfig"&&x(b,7)})}P(l,c)};H(o,l=>{m(i)&&l(a)})}P(e,r),et()}var S1=L('
                                    '),M1=L('
                                    '),P1=L('
                                    '),C1=L('
                                    '),A1=L('
                                    ',1);function T1(e,t){tt(t,!0);let n=j("overview");be(()=>{var E,R;document.title=Rg((R=(E=Lr.data)==null?void 0:E.active_project)==null?void 0:R.name)});const i=E=>E.catch(R=>console.debug("poll failed",R)),s=Zi(()=>i(Lr.poll()),1e3),r=Zi(()=>i(Ve.pollQueued()),1e3),o=Zi(()=>i(Ve.pollLast()),1e3),a=Zi(()=>i(yr.poll()),1e3),l=Zi(()=>i(di.poll()),1e3),c={config:s,queued:r,last:o,logs:a,timeline:l};function u(E){for(const R of Object.values(c))R.stop();if(!(typeof document<"u"&&document.visibilityState!=="visible"))for(const R of tg(E))c[R].start()}function d(E){O(n,E,!0),u(E)}dn(()=>{ri.init(),u("overview");const E=()=>{if(document.visibilityState==="visible")u(m(n));else for(const R of Object.values(c))R.stop()};return document.addEventListener("visibilitychange",E),()=>document.removeEventListener("visibilitychange",E)});var h=A1(),f=st(h),g=w(f);$f(g,{get active(){return m(n)},onnavigate:d,onshutdown:()=>Me.open({kind:"shutdown"})});var v=k(g,2),p=w(v);{var _=E=>{var R=S1(),I=w(R);$p(I,{onaddlanguage:()=>Me.open({kind:"addLanguage"}),onremovelanguage:C=>Me.open({kind:"removeLanguage",language:C}),oneditconfig:()=>Me.open({kind:"editSerenaConfig"}),onopenmemory:C=>Me.open({kind:"editMemory",name:C}),oncreatememory:()=>Me.open({kind:"createMemory"}),ondeletememory:C=>Me.open({kind:"deleteMemory",name:C}),oncancelexecution:C=>C.is_running?Me.open({kind:"cancelExecution",execution:C}):void Ve.cancel(C)}),P(E,R)};H(p,E=>{m(n)==="overview"&&E(_)})}var b=k(p,2);{var x=E=>{var R=M1(),I=w(R);ov(I,{}),P(E,R)};H(b,E=>{m(n)==="logs"&&E(x)})}var y=k(b,2);{var S=E=>{var R=P1(),I=w(R);f0(I,{onOpenInTimeline:C=>{di.setFilter(C),d("overview")}}),P(E,R)};H(y,E=>{m(n)==="stats"&&E(S)})}var D=k(y,2);{var T=E=>{var R=C1(),I=w(R);Z0(I,{}),P(E,R)};H(D,E=>{m(n)==="code"&&E(T)})}var A=k(f,2);k1(A,{}),P(e,h),et()}Sf(T1,{target:document.getElementById("app")}); diff --git a/src/serena/resources/dashboard/index.html b/src/serena/resources/dashboard/index.html index c941e9c86..ed00802d9 100644 --- a/src/serena/resources/dashboard/index.html +++ b/src/serena/resources/dashboard/index.html @@ -13,8 +13,8 @@ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" /> - - + +
                                    diff --git a/src/serena/task_executor.py b/src/serena/task_executor.py index 5e6242966..5ddb21241 100644 --- a/src/serena/task_executor.py +++ b/src/serena/task_executor.py @@ -1,4 +1,5 @@ import concurrent.futures +import re import threading import time from collections.abc import Callable @@ -14,6 +15,8 @@ log = logging.getLogger(__name__) T = TypeVar("T") +_TASK_PREFIX_RE = re.compile(r"^Task-\d+:\s*(.*)$") + class TaskExecutor: def __init__(self, name: str): @@ -38,6 +41,8 @@ def __init__(self, function: Callable[[], T], name: str, logged: bool = True, ti self.logged = logged self.timeout = timeout self._function = function + self.started_at: float | None = None + self.finished_at: float | None = None def _tostring_includes(self) -> list[str]: return ["name"] @@ -48,18 +53,22 @@ def start(self) -> None: """ def run_task() -> None: + self.started_at = time.time() try: if self.future.done(): if self.logged: log.info(f"Task {self.name} was already completed/cancelled; skipping execution") + self.finished_at = time.time() return with LogTime(self.name, logger=log, enabled=self.logged): result = self._function() if not self.future.done(): + self.finished_at = time.time() self.future.set_result(result) except Exception as e: if not self.future.done(): log.error(f"Error during execution of {self.name}: {e}", exc_info=e) + self.finished_at = time.time() self.future.set_exception(e) thread = Thread(target=run_task, name=self.name) @@ -143,13 +152,41 @@ class TaskInfo: unique identifier of the task """ logged: bool + started_at: float | None = None + finished_at: float | None = None def finished_successfully(self) -> bool: return self.future.done() and not self.future.cancelled() and self.future.exception() is None + def get_duration_ms(self) -> int | None: + if self.started_at is None or self.finished_at is None: + return None + return int(max(0.0, (self.finished_at - self.started_at) * 1000)) + + def get_error_message(self) -> str | None: + if not self.future.done() or self.future.cancelled(): + return None + exc = self.future.exception() + if exc is None: + return None + return f"{type(exc).__name__}: {exc}" + + def get_display_name(self) -> str: + """Strip "Task-N: " prefix if present.""" + m = _TASK_PREFIX_RE.match(self.name) + return m.group(1) if m else self.name + @staticmethod def from_task(task: "TaskExecutor.Task", is_running: bool) -> "TaskExecutor.TaskInfo": - return TaskExecutor.TaskInfo(name=task.name, is_running=is_running, future=task.future, task_id=id(task), logged=task.logged) + return TaskExecutor.TaskInfo( + name=task.name, + is_running=is_running, + future=task.future, + task_id=id(task), + logged=task.logged, + started_at=task.started_at, + finished_at=task.finished_at, + ) def cancel(self) -> None: self.future.cancel() diff --git a/src/serena/tools/tools_base.py b/src/serena/tools/tools_base.py index 89d064c38..45290ebe6 100644 --- a/src/serena/tools/tools_base.py +++ b/src/serena/tools/tools_base.py @@ -1,5 +1,6 @@ import inspect import json +import time from abc import ABC from collections.abc import Callable, Iterable from dataclasses import dataclass @@ -335,21 +336,28 @@ def task() -> str: if log_call: self._log_tool_application(inspect.currentframe(), session_id) + + apply_kwargs = dict(kwargs) + if self._is_session_aware: + apply_kwargs["session_id"] = session_id + + tool_name = self.get_name() + input_str = str(apply_kwargs) + result: str = "" + success = True + error_message: str | None = None + start = time.perf_counter() try: - # check whether the tool requires an active project and language server if not isinstance(self, ToolMarkerDoesNotRequireActiveProject): if self.agent.get_active_project() is None: - return ( + success = False + error_message = "No active project" + result = ( "Error: No active project. Ask the user to provide the project path or to select a project from this list of known projects: " + f"{self.agent.serena_config.project_names}" ) + return result - # construct apply kwargs, adding session_id if the tool is session-aware - apply_kwargs = dict(kwargs) - if self._is_session_aware: - apply_kwargs["session_id"] = session_id - - # apply the actual tool try: result = apply_fn(**apply_kwargs) except SolidLSPException as e: @@ -369,15 +377,24 @@ def task() -> str: else: raise - # record tool usage - self.agent.record_tool_usage(apply_kwargs, result, self) - except Exception as e: + success = False + error_message = f"{type(e).__name__}: {e}" if not catch_exceptions: raise msg = f"Error executing tool: {e.__class__.__name__} - {e}" log.error(f"Error executing tool: {e}", exc_info=e) result = msg + finally: + duration_ms = (time.perf_counter() - start) * 1000 + self.agent._record_tool_call_safely( + tool_name=tool_name, + input_str=input_str, + output_str=result, + duration_ms=duration_ms, + success=success, + error_message=error_message, + ) if log_call: log.info(f"Result: {result}") diff --git a/test/serena/test_analytics.py b/test/serena/test_analytics.py new file mode 100644 index 000000000..744e63aee --- /dev/null +++ b/test/serena/test_analytics.py @@ -0,0 +1,269 @@ +from serena.analytics import ToolUsageStats + + +def test_entry_update_on_call_tracks_timing_and_errors(): + stats = ToolUsageStats() + # Successful call + stats._tool_stats["read_file"].update_on_call( + input_tokens=10, output_tokens=20, duration_ms=15.0, success=True, now=1000.0 + ) + e = stats._tool_stats["read_file"] + assert e.num_times_called == 1 + assert e.num_errors == 0 + assert e.input_tokens == 10 + assert e.output_tokens == 20 + assert e.total_duration_ms == 15.0 + assert e.min_duration_ms == 15.0 + assert e.max_duration_ms == 15.0 + assert e.last_called_at == 1000.0 + # Failed call with longer duration + stats._tool_stats["read_file"].update_on_call( + input_tokens=5, output_tokens=0, duration_ms=42.0, success=False, now=1001.0 + ) + e = stats._tool_stats["read_file"] + assert e.num_times_called == 2 + assert e.num_errors == 1 + assert e.total_duration_ms == 57.0 + assert e.min_duration_ms == 15.0 + assert e.max_duration_ms == 42.0 + assert e.last_called_at == 1001.0 + + +from serena.analytics import ToolCallRecord, _truncate_preview, _INPUT_OUTPUT_PREVIEW_BYTES + + +def test_truncate_preview_short_input_returns_as_is(): + text, truncated = _truncate_preview("hello") + assert text == "hello" + assert truncated is False + + +def test_truncate_preview_long_input_truncates_to_cap(): + long = "x" * (_INPUT_OUTPUT_PREVIEW_BYTES + 1000) + text, truncated = _truncate_preview(long) + assert len(text.encode("utf-8")) <= _INPUT_OUTPUT_PREVIEW_BYTES + assert truncated is True + + +def test_tool_call_record_is_frozen(): + rec = ToolCallRecord( + seq=1, tool="read_file", started_at=1000.0, duration_ms=12.0, + success=True, error_message=None, + input_preview="a", output_preview="b", + input_truncated=False, output_truncated=False, + ) + import dataclasses + assert dataclasses.is_dataclass(rec) + # Frozen — assignment raises + import pytest as _pytest + with _pytest.raises(dataclasses.FrozenInstanceError): + rec.seq = 2 # type: ignore[misc] + + +def test_record_tool_call_safely_handles_analytics_exception(monkeypatch, caplog): + """ + Instrumentation must never break the agent: if record_call raises, + _record_tool_call_safely swallows and logs. + """ + import logging + from serena.agent import SerenaAgent + + # Build a minimal agent without going through SerenaAgent.__init__'s heavy setup. + agent = SerenaAgent.__new__(SerenaAgent) + agent._tool_usage_stats = ToolUsageStats() + + def explode(*_a, **_kw): + raise RuntimeError("synthetic analytics failure") + + monkeypatch.setattr(agent._tool_usage_stats, "record_call", explode) + + with caplog.at_level(logging.WARNING): + # Must not raise. + agent._record_tool_call_safely( + tool_name="x", input_str="i", output_str="o", + duration_ms=1.0, success=True, error_message=None, + ) + assert any("synthetic analytics failure" in r.message or "analytics" in r.message.lower() for r in caplog.records) + + +def test_record_tool_call_safely_records_success_path(monkeypatch): + """ + A successful call recorded via _record_tool_call_safely shows up in the + timeline buffer with success=True and the given duration. + """ + from serena.agent import SerenaAgent + + agent = SerenaAgent.__new__(SerenaAgent) + agent._tool_usage_stats = ToolUsageStats() + + agent._record_tool_call_safely( + tool_name="fake_tool", input_str="{}", output_str="ok", + duration_ms=12.5, success=True, error_message=None, + ) + recs, _ = agent._tool_usage_stats.get_records_since(since_seq=None, tool=None, limit=10) + assert len(recs) == 1 + assert recs[0].tool == "fake_tool" + assert recs[0].duration_ms == 12.5 + assert recs[0].success is True + + +def test_apply_ex_records_exactly_once_on_exception(monkeypatch): + """B1 regression: apply_ex with catch_exceptions=True records the failing + call exactly once via the finally block, not twice.""" + from unittest.mock import MagicMock, patch + from serena.agent import SerenaAgent + from serena.tools.tools_base import Tool, ToolMarkerDoesNotRequireActiveProject + + # Build a minimal Tool subclass that doesn't need a project and always raises. + class _RaisingTool(Tool, ToolMarkerDoesNotRequireActiveProject): + """A minimal tool that always raises for testing.""" + + def apply(self) -> str: # noqa: D102 + """Apply the tool.""" + raise RuntimeError("deliberate failure") + + # Build a minimal agent stub. + agent = SerenaAgent.__new__(SerenaAgent) + agent._tool_usage_stats = ToolUsageStats() + + record_calls: list[dict] = [] + + def _capture_record(**kwargs): # type: ignore[override] + record_calls.append(kwargs) + + agent._record_tool_call_safely = _capture_record # type: ignore[method-assign] + + # Stub out is_active so the tool is always active. + tool = _RaisingTool.__new__(_RaisingTool) + tool.agent = agent + monkeypatch.setattr(tool, "is_active", lambda: True) + + # Stub issue_task to run the callable inline (bypasses TaskExecutor). + def _inline_issue_task(fn, name=""): + result_holder = {} + + class _FakeFuture: + def result(self, timeout=None): + return result_holder["value"] + + result_holder["value"] = fn() + return _FakeFuture() + + agent.issue_task = _inline_issue_task # type: ignore[method-assign] + + tool.apply_ex(log_call=False, catch_exceptions=True) + + assert len(record_calls) == 1, f"Expected 1 recording, got {len(record_calls)}" + assert record_calls[0]["success"] is False + assert "deliberate failure" in (record_calls[0]["error_message"] or "") + + +def test_failed_tool_call_is_recorded_with_error_message(): + from serena.agent import SerenaAgent + agent = SerenaAgent.__new__(SerenaAgent) + agent._tool_usage_stats = ToolUsageStats() + agent._record_tool_call_safely( + tool_name="bad_tool", input_str="{}", output_str="", + duration_ms=3.0, success=False, error_message="ValueError: bad arg", + ) + recs, _ = agent._tool_usage_stats.get_records_since(since_seq=None, tool=None, limit=10) + assert len(recs) == 1 + assert recs[0].success is False + assert recs[0].error_message == "ValueError: bad arg" + e = agent._tool_usage_stats.get_stats("bad_tool") + assert e.num_errors == 1 + + +import threading +from serena.analytics import _RECORD_BUFFER_SIZE + + +def test_record_call_populates_buffer_and_entry(): + stats = ToolUsageStats() + stats.record_call( + tool_name="read_file", input_str="a=1", output_str="ok", + duration_ms=5.0, success=True, error_message=None, now=1000.0, + ) + recs, max_seq = stats.get_records_since(since_seq=None, tool=None, limit=10) + assert max_seq == 1 + assert len(recs) == 1 + r = recs[0] + assert r.seq == 1 + assert r.tool == "read_file" + assert r.success is True + assert r.duration_ms == 5.0 + e = stats.get_stats("read_file") + assert e.num_times_called == 1 + assert e.total_duration_ms == 5.0 + + +def test_get_records_since_cursor_filters(): + stats = ToolUsageStats() + for i in range(5): + stats.record_call( + tool_name=f"t{i % 2}", input_str="", output_str="", + duration_ms=1.0, success=True, error_message=None, now=1000.0 + i, + ) + recs, max_seq = stats.get_records_since(since_seq=2, tool=None, limit=10) + assert [r.seq for r in recs] == [3, 4, 5] + assert max_seq == 5 + recs_t0, _ = stats.get_records_since(since_seq=None, tool="t0", limit=10) + assert all(r.tool == "t0" for r in recs_t0) + assert [r.seq for r in recs_t0] == [1, 3, 5] + + +def test_ring_buffer_drops_oldest_at_capacity(): + stats = ToolUsageStats() + for i in range(_RECORD_BUFFER_SIZE + 50): + stats.record_call( + tool_name="t", input_str="", output_str="", + duration_ms=1.0, success=True, error_message=None, now=float(i), + ) + recs, max_seq = stats.get_records_since(since_seq=None, tool=None, limit=_RECORD_BUFFER_SIZE + 100) + assert max_seq == _RECORD_BUFFER_SIZE + 50 + assert len(recs) == _RECORD_BUFFER_SIZE + # Earliest retained is seq = max_seq - cap + 1 + assert recs[0].seq == max_seq - _RECORD_BUFFER_SIZE + 1 + + +def test_clear_resets_records_and_seq_counter(): + stats = ToolUsageStats() + stats.record_call(tool_name="t", input_str="", output_str="", + duration_ms=1.0, success=True, error_message=None, now=1.0) + assert len(stats.get_records_since(since_seq=None, tool=None, limit=10)[0]) == 1 + stats.clear() + records, max_seq = stats.get_records_since(since_seq=None, tool=None, limit=10) + assert records == [] + assert max_seq == 0 + # The next record_call resumes seq numbering from 1 (not 2). + stats.record_call(tool_name="t", input_str="", output_str="", + duration_ms=1.0, success=True, error_message=None, now=2.0) + records, max_seq = stats.get_records_since(since_seq=None, tool=None, limit=10) + assert records[0].seq == 1 + assert max_seq == 1 + + +def test_seq_monotonic_under_concurrent_writers(): + stats = ToolUsageStats() + N = 1000 + + def writer(): + for _ in range(N): + stats.record_call( + tool_name="t", input_str="", output_str="", + duration_ms=1.0, success=True, error_message=None, now=0.0, + ) + + threads = [threading.Thread(target=writer) for _ in range(2)] + for t in threads: + t.start() + for t in threads: + t.join() + recs, max_seq = stats.get_records_since(since_seq=None, tool=None, limit=10_000) + assert max_seq == 2 * N + assert len(recs) == 2 * N # *** B7 correction: completeness check *** + # No duplicate seqs in retained tail + seqs = [r.seq for r in recs] + assert len(seqs) == len(set(seqs)) + # Strictly increasing + assert seqs == sorted(seqs) diff --git a/test/serena/test_dashboard.py b/test/serena/test_dashboard.py index 37e68e010..faa024072 100644 --- a/test/serena/test_dashboard.py +++ b/test/serena/test_dashboard.py @@ -1,5 +1,8 @@ from types import SimpleNamespace +import pytest + +from serena.analytics import ToolUsageStats from serena.dashboard import SerenaDashboardAPI from solidlsp.ls_config import Language @@ -12,9 +15,30 @@ def clear_log_messages(self) -> None: # pragma: no cover - simple stub pass +class _DummyContext: + name = "test-context" + description = "test context description" + _yaml_path = "/tmp/test-context.yaml" + + +class _DummyMode: + name = "test-mode" + description = "test mode description" + _yaml_path = "/tmp/test-mode.yaml" + + +class _DummyLanguageBackend: + def is_jetbrains(self) -> bool: + return False + + class _DummyAgent: - def __init__(self, project: SimpleNamespace | None) -> None: + version = "test-version" + + def __init__(self, project: SimpleNamespace | None = None) -> None: self._project = project + self._all_tools: dict = {} + self.serena_config = SimpleNamespace(projects=[]) def execute_task(self, func, *, logged: bool | None = None, name: str | None = None): del logged, name @@ -23,6 +47,27 @@ def execute_task(self, func, *, logged: bool | None = None, name: str | None = N def get_active_project(self): return self._project + def get_current_tasks(self): + return [] + + def get_last_executed_task(self): + return None + + def get_context(self): + return _DummyContext() + + def get_active_modes(self): + return [] + + def get_active_tool_names(self): + return [] + + def tool_is_active(self, _name: str) -> bool: + return False + + def get_language_backend(self): + return _DummyLanguageBackend() + def _make_dashboard(project_languages: list[Language] | None) -> SerenaDashboardAPI: project = None @@ -32,6 +77,24 @@ def _make_dashboard(project_languages: list[Language] | None) -> SerenaDashboard return SerenaDashboardAPI(memory_log_handler=_DummyMemoryLogHandler(), tool_names=[], agent=agent, tool_usage_stats=None) +@pytest.fixture +def make_dashboard_with_stats(): + """Returns a callable (stats) -> (dashboard, client) where client is a Flask test client.""" + + def _factory(stats: ToolUsageStats | None = None): + agent = _DummyAgent() + stats = stats if stats is not None else ToolUsageStats() + dashboard = SerenaDashboardAPI( + memory_log_handler=_DummyMemoryLogHandler(), + tool_names=[], + agent=agent, + tool_usage_stats=stats, + ) + return dashboard, dashboard._app.test_client() + + return _factory + + def test_available_languages_include_experimental_when_no_active_project(): dashboard = _make_dashboard(project_languages=None) response = dashboard._get_available_languages() @@ -47,3 +110,138 @@ def test_available_languages_exclude_project_languages(): assert Language.MARKDOWN.value not in available # ensure experimental languages remain available for selection assert Language.ANSIBLE.value in available + + +# --------------------------------------------------------------------------- +# Task 6 — /get_tool_call_timeline +# --------------------------------------------------------------------------- + + +def test_get_tool_call_timeline_returns_records_with_cursor(make_dashboard_with_stats): + dashboard, client = make_dashboard_with_stats() + stats = dashboard._tool_usage_stats + for i in range(5): + stats.record_call( + tool_name="t", + input_str="", + output_str="", + duration_ms=1.0, + success=True, + error_message=None, + now=1000.0 + i, + ) + r = client.get("/get_tool_call_timeline") + assert r.status_code == 200 + body = r.get_json() + assert body["max_seq"] == 5 + assert len(body["records"]) == 5 + + r = client.get("/get_tool_call_timeline?since_seq=3") + body = r.get_json() + assert [rec["seq"] for rec in body["records"]] == [4, 5] + + stats.record_call( + tool_name="other", + input_str="", + output_str="", + duration_ms=1.0, + success=True, + error_message=None, + now=2000.0, + ) + r = client.get("/get_tool_call_timeline?tool=other") + body = r.get_json() + assert all(rec["tool"] == "other" for rec in body["records"]) + + r = client.get("/get_tool_call_timeline?limit=99999") + assert r.status_code == 200 # capped server-side, no error + + r = client.get("/get_tool_call_timeline?since_seq=-1") + assert r.status_code == 400 # 400 on negative cursor + + +def test_get_tool_call_timeline_empty_when_no_stats(): + """When tool_usage_stats is None, returns empty payload.""" + dashboard = SerenaDashboardAPI( + memory_log_handler=_DummyMemoryLogHandler(), + tool_names=[], + agent=_DummyAgent(), + tool_usage_stats=None, + ) + client = dashboard._app.test_client() + r = client.get("/get_tool_call_timeline") + assert r.status_code == 200 + assert r.get_json() == {"records": [], "max_seq": 0} + + +# --------------------------------------------------------------------------- +# Task 7 — tool_stats_totals +# --------------------------------------------------------------------------- + + +def test_config_overview_includes_tool_stats_totals(make_dashboard_with_stats): + dashboard, _client = make_dashboard_with_stats() + stats = dashboard._tool_usage_stats + stats.record_call( + tool_name="t", + input_str="abc", + output_str="defg", + duration_ms=10.0, + success=True, + error_message=None, + now=1000.0, + ) + stats.record_call( + tool_name="t", + input_str="x", + output_str="y", + duration_ms=20.0, + success=False, + error_message="Err: nope", + now=1001.0, + ) + response = dashboard._get_config_overview() + totals = response.tool_stats_totals + assert totals["num_calls"] == 2 + assert totals["num_errors"] == 1 + assert totals["total_duration_ms"] == 30.0 + assert totals["total_tokens"] >= 0 + + +# --------------------------------------------------------------------------- +# Task 8 — QueuedExecution extension +# --------------------------------------------------------------------------- + + +def test_queued_execution_includes_timing_and_error_fields(): + from serena.dashboard import QueuedExecution + from serena.task_executor import TaskExecutor + + task = TaskExecutor.Task(function=lambda: "ok", name="Task-7: read_file", logged=False) + task.start() + task.wait_until_done(timeout=2.0) + info = TaskExecutor.TaskInfo.from_task(task, is_running=False) + serialized = QueuedExecution.from_task_info(info).model_dump() + + assert serialized["display_name"] == "read_file" + assert serialized["duration_ms"] is not None + assert serialized["error_message"] is None + assert serialized["started_at"] is not None + assert serialized["finished_at"] is not None + + +def test_queued_executions_route_returns_extended_payload(make_dashboard_with_stats): + from serena.task_executor import TaskExecutor + + dashboard, client = make_dashboard_with_stats() + task = TaskExecutor.Task(function=lambda: "ok", name="Task-1: foo", logged=False) + task.start() + task.wait_until_done(timeout=2.0) + info = TaskExecutor.TaskInfo.from_task(task, is_running=False) + dashboard._agent.get_current_tasks = lambda: [info] + + r = client.get("/queued_task_executions") + body = r.get_json() + assert body["status"] == "success" + assert body["queued_executions"][0]["display_name"] == "foo" + assert body["queued_executions"][0]["duration_ms"] is not None diff --git a/test/serena/test_dashboard_code.py b/test/serena/test_dashboard_code.py new file mode 100644 index 000000000..9bd0bbe85 --- /dev/null +++ b/test/serena/test_dashboard_code.py @@ -0,0 +1,386 @@ +"""Tests for src/serena/dashboard_code.py — the /code/* endpoints.""" + +from __future__ import annotations + +import os +from pathlib import Path +from types import SimpleNamespace + +import pytest + +from serena.dashboard_code import LSPNotReady, resolve_project_path + + +# ----------------------------------------------------------------------------- +# Task 19 — path resolve + LSPNotReady +# ----------------------------------------------------------------------------- + + +def test_resolve_project_path_rejects_traversal(tmp_path): + root = tmp_path / "proj" + root.mkdir() + (root / "file.py").write_text("x = 1") + assert resolve_project_path(str(root), "file.py") == str((root / "file.py").resolve()) + with pytest.raises(ValueError): + resolve_project_path(str(root), "../secret.txt") + with pytest.raises(ValueError): + resolve_project_path(str(root), "/etc/passwd") + + +def test_resolve_project_path_rejects_nul_byte(tmp_path): + root = tmp_path / "proj" + root.mkdir() + with pytest.raises(ValueError): + resolve_project_path(str(root), "foo\x00.txt") + + +@pytest.mark.skipif(os.name == "nt", reason="symlinks require admin/dev mode on Windows") +def test_resolve_project_path_rejects_symlink_escape(tmp_path): + root = tmp_path / "proj" + root.mkdir() + outside = tmp_path / "outside.txt" + outside.write_text("bad") + os.symlink(str(outside), str(root / "link.txt")) + with pytest.raises(ValueError): + resolve_project_path(str(root), "link.txt") + + +def test_resolve_project_path_rejects_missing_file(tmp_path): + root = tmp_path / "proj" + root.mkdir() + with pytest.raises(FileNotFoundError): + resolve_project_path(str(root), "missing.py") + + +def test_lsp_not_ready_is_an_exception_type(): + assert issubclass(LSPNotReady, Exception) + + +# ----------------------------------------------------------------------------- +# Shared fixtures / fakes +# ----------------------------------------------------------------------------- + + +class _DummyMemLog: + def get_log_messages(self, from_idx=0): + return SimpleNamespace(messages=[], max_idx=-1) + + def clear_log_messages(self): + pass + + +class _DummyAgent: + version = "test-version" + + def __init__(self, root, ls_manager=None): + self._project = SimpleNamespace( + project_root=str(root), + project_name="proj", + project_config=SimpleNamespace(languages=[], encoding=None), + ) + self._ls_manager = ls_manager + self._all_tools: dict = {} + self.serena_config = SimpleNamespace(projects=[]) + + def get_active_project(self): + return self._project + + def get_language_server_manager(self): + return self._ls_manager + + def get_current_tasks(self): + return [] + + def get_last_executed_task(self): + return None + + def execute_task(self, fn, *, logged=None, name=None): + return fn() + + def get_context(self): + return SimpleNamespace(name="test", description="", _yaml_path="/tmp/x") + + def get_active_modes(self): + return [] + + def get_active_tool_names(self): + return [] + + def tool_is_active(self, _name): + return False + + def get_language_backend(self): + return SimpleNamespace(is_jetbrains=lambda: False) + + +@pytest.fixture +def make_dashboard_with_project(tmp_path): + from serena.dashboard import SerenaDashboardAPI + + def _factory(ls_manager=None): + root = tmp_path / "proj" + root.mkdir() + agent = _DummyAgent(root, ls_manager=ls_manager) + dashboard = SerenaDashboardAPI( + memory_log_handler=_DummyMemLog(), + tool_names=[], + agent=agent, + tool_usage_stats=None, + ) + return dashboard, dashboard._app.test_client(), root + + return _factory + + +# ----------------------------------------------------------------------------- +# Task 20 — /code/list_dir +# ----------------------------------------------------------------------------- + + +def test_code_list_dir_returns_entries(make_dashboard_with_project): + _, client, root = make_dashboard_with_project() + (root / "src").mkdir() + (root / "src" / "main.py").write_text("print('hi')") + (root / "README.md").write_text("hi") + r = client.get("/code/list_dir?path=.") + assert r.status_code == 200 + body = r.get_json() + names = {e["name"] for e in body["entries"]} + assert "src" in names and "README.md" in names + kinds = {e["name"]: e["kind"] for e in body["entries"]} + assert kinds["src"] == "dir" and kinds["README.md"] == "file" + + +def test_code_list_dir_rejects_traversal(make_dashboard_with_project): + _, client, _root = make_dashboard_with_project() + r = client.get("/code/list_dir?path=../etc") + assert r.status_code == 400 + + +def test_code_list_dir_respects_gitignore(make_dashboard_with_project): + _, client, root = make_dashboard_with_project() + (root / ".gitignore").write_text("ignored/\n") + (root / "ignored").mkdir() + (root / "visible").mkdir() + r = client.get("/code/list_dir?path=.") + names = {e["name"] for e in r.get_json()["entries"]} + assert "visible" in names + assert "ignored" not in names + + +# ----------------------------------------------------------------------------- +# Task 21 — /code/file_symbols +# ----------------------------------------------------------------------------- + + +class _FakeLS: + def __init__(self, doc_symbols=None, raise_exc=None, ws_symbols=None, diagnostics_map=None): + self._doc_symbols = doc_symbols + self._raise = raise_exc + self._ws_symbols = ws_symbols + self._diagnostics_map = diagnostics_map or {} + + def request_document_symbols(self, rel): + if self._raise: + raise self._raise + return self._doc_symbols or [] + + def request_workspace_symbol(self, query): + if self._raise: + raise self._raise + return self._ws_symbols + + def request_published_text_document_diagnostics(self, rel, **_kw): + if self._raise: + raise self._raise + return self._diagnostics_map.get(rel) + + +class _FakeManager: + def __init__(self, ls=None): + self._ls = ls + + def get_language_server(self, rel): + if self._ls is None: + raise ValueError("no LS") + return self._ls + + def iter_language_servers(self): + if self._ls: + yield self._ls + + +def test_code_file_symbols_returns_document_symbols(make_dashboard_with_project): + fake_syms = [ + { + "name": "Foo", + "kind": 5, + "range": {"start": {"line": 0, "character": 0}, "end": {"line": 5, "character": 0}}, + "children": [ + { + "name": "bar", + "kind": 6, + "range": {"start": {"line": 1, "character": 4}, "end": {"line": 3, "character": 0}}, + "children": [], + } + ], + } + ] + mgr = _FakeManager(ls=_FakeLS(doc_symbols=fake_syms)) + _, client, root = make_dashboard_with_project(ls_manager=mgr) + (root / "main.py").write_text("class Foo:\n def bar(self): pass\n") + r = client.get("/code/file_symbols?path=main.py") + assert r.status_code == 200 + body = r.get_json() + assert body["symbols"][0]["name"] == "Foo" + assert body["symbols"][0]["kind"] == "Class" + assert body["symbols"][0]["children"][0]["name"] == "bar" + assert body["symbols"][0]["children"][0]["kind"] == "Method" + + +def test_code_file_symbols_503_when_no_ls(make_dashboard_with_project): + _, client, root = make_dashboard_with_project() + (root / "f.py").write_text("x=1") + r = client.get("/code/file_symbols?path=f.py") + assert r.status_code == 503 + assert r.get_json()["code"] == "ls_not_ready" + + +def test_code_file_symbols_404_for_missing(make_dashboard_with_project): + _, client, _root = make_dashboard_with_project() + r = client.get("/code/file_symbols?path=nope.py") + assert r.status_code == 404 + + +# ----------------------------------------------------------------------------- +# Task 22 — /code/workspace_symbol_search +# ----------------------------------------------------------------------------- + + +def test_code_workspace_symbol_search_slices_to_limit(make_dashboard_with_project, tmp_path): + # Build 5 fake matches, request limit=2, assert 2 returned. + matches = [] + for i in range(5): + matches.append( + { + "name": f"foo{i}", + "kind": 12, + "location": { + "uri": "file:///nonexistent/main.py", + "range": {"start": {"line": i, "character": 0}, "end": {"line": i, "character": 1}}, + }, + } + ) + mgr = _FakeManager(ls=_FakeLS(ws_symbols=matches)) + _, client, _root = make_dashboard_with_project(ls_manager=mgr) + r = client.get("/code/workspace_symbol_search?q=foo&limit=2") + assert r.status_code == 200 + body = r.get_json() + assert len(body["matches"]) == 2 + assert body["matches"][0]["name"] == "foo0" + assert body["matches"][0]["kind"] == "Function" + + +def test_code_workspace_symbol_search_503_when_no_ls(make_dashboard_with_project): + _, client, _root = make_dashboard_with_project() + r = client.get("/code/workspace_symbol_search?q=foo") + assert r.status_code == 503 + assert r.get_json()["code"] == "ls_not_ready" + + +def test_code_workspace_symbol_search_empty_query_returns_empty(make_dashboard_with_project): + # Even with no LS, empty query short-circuits to 200/{matches:[]}. + _, client, _root = make_dashboard_with_project() + r = client.get("/code/workspace_symbol_search?q=") + assert r.status_code == 200 + assert r.get_json() == {"matches": []} + + +# ----------------------------------------------------------------------------- +# Task 23 — /code/diagnostics_summary +# ----------------------------------------------------------------------------- + + +def test_code_diagnostics_summary_503_when_no_ls(make_dashboard_with_project): + _, client, root = make_dashboard_with_project() + (root / "a.py").write_text("x=1") + r = client.get("/code/diagnostics_summary") + assert r.status_code == 503 + assert r.get_json()["code"] == "ls_not_ready" + + +def test_code_diagnostics_summary_503_when_no_project(tmp_path): + from serena.dashboard import SerenaDashboardAPI + + class _AgentNoProject: + version = "v" + _all_tools: dict = {} + serena_config = SimpleNamespace(projects=[]) + + def get_active_project(self): + return None + + def get_language_server_manager(self): + return None + + def get_current_tasks(self): + return [] + + def get_last_executed_task(self): + return None + + def execute_task(self, fn, *, logged=None, name=None): + return fn() + + def get_language_backend(self): + return SimpleNamespace(is_jetbrains=lambda: False) + + dashboard = SerenaDashboardAPI( + memory_log_handler=_DummyMemLog(), tool_names=[], agent=_AgentNoProject(), tool_usage_stats=None + ) + client = dashboard._app.test_client() + r = client.get("/code/diagnostics_summary") + assert r.status_code == 503 + assert r.get_json()["code"] == "no_project" + + +def test_code_diagnostics_summary_happy_path(make_dashboard_with_project): + diag = { + "range": {"start": {"line": 3, "character": 2}, "end": {"line": 3, "character": 5}}, + "severity": 1, + "message": "undefined variable", + "source": "pyright", + } + mgr = _FakeManager(ls=_FakeLS(diagnostics_map={"a.py": [diag]})) + _, client, root = make_dashboard_with_project(ls_manager=mgr) + (root / "a.py").write_text("x=1\n") + r = client.get("/code/diagnostics_summary") + assert r.status_code == 200 + body = r.get_json() + assert body["truncated"] is False + files = {f["path"]: f for f in body["files"]} + assert "a.py" in files + assert files["a.py"]["diagnostics"][0]["severity"] == "error" + assert files["a.py"]["diagnostics"][0]["line"] == 3 + assert files["a.py"]["diagnostics"][0]["column"] == 2 + assert files["a.py"]["diagnostics"][0]["source"] == "pyright" + + +def test_code_diagnostics_summary_truncates_long_message(make_dashboard_with_project): + long_msg = "X" * 10_000 + diag = { + "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 1}}, + "severity": 2, + "message": long_msg, + "source": "x", + } + mgr = _FakeManager(ls=_FakeLS(diagnostics_map={"a.py": [diag]})) + _, client, root = make_dashboard_with_project(ls_manager=mgr) + (root / "a.py").write_text("x=1\n") + r = client.get("/code/diagnostics_summary") + assert r.status_code == 200 + body = r.get_json() + assert body["truncated"] is True + files = {f["path"]: f for f in body["files"]} + msg = files["a.py"]["diagnostics"][0]["message"] + assert len(msg) <= 4096 diff --git a/test/serena/test_task_executor.py b/test/serena/test_task_executor.py index baf78bb1a..a164665d3 100644 --- a/test/serena/test_task_executor.py +++ b/test/serena/test_task_executor.py @@ -124,3 +124,80 @@ def test_task_executor_cancellation_via_task_info(executor): pass end_time = time.time() assert (end_time - start_time) < 9, "Cancelled task did not stop in time" + + +import threading + + +def test_task_records_started_and_finished_at_on_success(): + task = TaskExecutor.Task(function=lambda: "ok", name="t1", logged=False) + task.start() + task.wait_until_done(timeout=2.0) + assert task.started_at is not None + assert task.finished_at is not None + assert task.finished_at >= task.started_at + + +def test_task_records_finished_at_on_failure(): + def boom() -> str: + raise RuntimeError("nope") + + task = TaskExecutor.Task(function=boom, name="t2", logged=False) + task.start() + task.wait_until_done(timeout=2.0) + assert task.finished_at is not None + assert task.future.exception() is not None + + +def test_task_info_get_duration_and_error_message(): + task_ok = TaskExecutor.Task(function=lambda: "ok", name="t3", logged=False) + task_ok.start() + task_ok.wait_until_done(timeout=2.0) + info_ok = TaskExecutor.TaskInfo.from_task(task_ok, is_running=False) + assert info_ok.get_duration_ms() is not None + assert info_ok.get_duration_ms() >= 0 + assert info_ok.get_error_message() is None + + def boom() -> str: + raise RuntimeError("kaboom") + + task_err = TaskExecutor.Task(function=boom, name="t4", logged=False) + task_err.start() + task_err.wait_until_done(timeout=2.0) + info_err = TaskExecutor.TaskInfo.from_task(task_err, is_running=False) + assert info_err.get_error_message() is not None + assert "kaboom" in info_err.get_error_message() + + +def test_task_info_get_display_name_strips_task_n_prefix(): + task = TaskExecutor.Task(function=lambda: "ok", name="Task-7: find_symbol", logged=False) + info = TaskExecutor.TaskInfo.from_task(task, is_running=False) + assert info.get_display_name() == "find_symbol" + task2 = TaskExecutor.Task(function=lambda: "ok", name="naked-name", logged=False) + info2 = TaskExecutor.TaskInfo.from_task(task2, is_running=False) + assert info2.get_display_name() == "naked-name" + + +def test_finished_at_visible_before_future_done_callback_fires(): + """ + Race-fix regression: if finished_at is set AFTER set_result, a done-callback + observer may see finished_at == None. We assert the inverse: every done-callback + observation sees finished_at populated. + """ + observations: list[bool] = [] + barrier = threading.Event() + + for _ in range(100): + task = TaskExecutor.Task(function=lambda: "ok", name="race", logged=False) + + def cb(_fut, t=task): + observations.append(t.finished_at is not None) + if len(observations) == 100: + barrier.set() + + task.future.add_done_callback(cb) + task.start() + + barrier.wait(timeout=10.0) + assert len(observations) == 100 + assert all(observations), "finished_at was None for one or more done-callback observations" From be957b39b8aee158c540c77a0db0643337e0d004 Mon Sep 17 00:00:00 2001 From: Paul Basanets Date: Sat, 30 May 2026 00:33:08 +0300 Subject: [PATCH 4/8] feat(dashboard): Code-tab redesign, scoped diagnostics & file icons Follow-on feature work building on the Code tab. - Code tab redesign with a lucide icon system (Icon wrapper plus icons across FileTree, Timeline, shell, logs, modals and drilldown). - symbolTree utilities (KIND_META, countByKind, filterTree, flattenForDisplay, symbolKey) and a rebuilt FileSymbols (breadcrumb, filter, copy). - Tri-state theme switcher, drilldown redesign, 2-col CodePage with Diagnostics as a 3rd middle pane, severity chips and slow-warning. - Scoped file/directory diagnostics: scope selector, per-row Run action, scope-aware text, pinned explicit scope. - Code-tab file-icon mapping module, type icons, indent guides and a how-it-works explainer popover (reusable Popover primitive). Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 3 + dashboard/package-lock.json | 36 +- dashboard/package.json | 3 +- dashboard/src/App.svelte | 2 - dashboard/src/components/code/CodePage.svelte | 168 +- .../components/code/DiagnosticsPanel.svelte | 258 +- .../src/components/code/FileSymbols.svelte | 371 ++- dashboard/src/components/code/FileTree.svelte | 134 +- .../components/code/WorkspaceSearch.svelte | 32 +- .../src/components/common/Collapsible.svelte | 4 +- .../src/components/common/Combobox.svelte | 5 +- .../components/common/FilterDropdown.svelte | 11 +- dashboard/src/components/common/Icon.svelte | 26 + .../src/components/common/Popover.svelte | 105 + .../src/components/logs/LogToolbar.svelte | 4 +- .../components/modals/EditMemoryModal.svelte | 6 +- .../overview/CancelledExecutions.svelte | 44 - .../src/components/overview/ConfigCard.svelte | 6 +- .../overview/ExecutionsQueue.svelte | 74 - .../components/overview/LastExecution.svelte | 94 - .../components/overview/OverviewPage.svelte | 20 +- .../components/overview/ProjectsPanel.svelte | 2 +- .../src/components/overview/Timeline.svelte | 135 +- .../components/overview/TimelineRow.svelte | 362 ++- dashboard/src/components/shell/Header.svelte | 18 +- .../src/components/shell/ThemeToggle.svelte | 32 +- .../components/stats/DrillDownPanel.svelte | 346 ++- dashboard/src/lib/api/endpoints.ts | 14 +- dashboard/src/lib/api/types.ts | 6 +- dashboard/src/lib/fileIcons.ts | 145 + dashboard/src/lib/format.ts | 93 + dashboard/src/lib/pollers.ts | 4 +- dashboard/src/lib/stores/clock.svelte.ts | 31 + dashboard/src/lib/stores/code.svelte.ts | 139 +- dashboard/src/lib/stores/executions.svelte.ts | 13 +- dashboard/src/lib/stores/theme.svelte.ts | 36 +- dashboard/src/lib/stores/timeline.svelte.ts | 77 +- dashboard/src/lib/symbolTree.ts | 135 + dashboard/src/lib/timelineRows.ts | 82 + dashboard/tests/charts.test.ts | 2 + dashboard/tests/code-page.test.ts | 64 + dashboard/tests/code-store.test.ts | 216 ++ dashboard/tests/diagnostics-panel.test.ts | 128 + dashboard/tests/executions-queue.test.ts | 48 - dashboard/tests/executions-store.test.ts | 25 - dashboard/tests/file-icons.test.ts | 63 + dashboard/tests/file-symbols.test.ts | 113 + dashboard/tests/file-tree.test.ts | 68 + dashboard/tests/format.test.ts | 51 +- dashboard/tests/icon.test.ts | 30 + dashboard/tests/last-execution.test.ts | 38 - dashboard/tests/pollers.test.ts | 4 +- dashboard/tests/popover.test.ts | 41 + dashboard/tests/setup.ts | 11 + dashboard/tests/symbol-tree.test.ts | 169 ++ dashboard/tests/theme.test.ts | 35 +- dashboard/tests/timeline-row.test.ts | 73 + dashboard/tests/timeline-rows.test.ts | 107 + dashboard/tests/timeline-store.test.ts | 8 +- dashboard/tests/timeline.test.ts | 91 +- dashboard/vite.config.ts | 1 - ...026-05-29-code-tab-file-icons-explainer.md | 896 ++++++ .../2026-05-29-code-tab-visual-redesign.md | 2676 +++++++++++++++++ .../plans/2026-05-29-scoped-diagnostics.md | 926 ++++++ ...29-code-tab-file-icons-explainer-design.md | 211 ++ ...6-05-29-code-tab-visual-redesign-design.md | 230 ++ .../2026-05-29-scoped-diagnostics-design.md | 251 ++ pyproject.toml | 2 +- src/serena/agent.py | 10 +- src/serena/analytics.py | 13 +- src/serena/dashboard.py | 13 +- src/serena/dashboard_code.py | 280 +- .../dashboard/assets/index-CtWjMwlp.css | 1 + .../dashboard/assets/index-DENnORmv.js | 55 + .../dashboard/assets/index-i8LjQRS6.css | 1 - .../dashboard/assets/index-vCsKzPK1.js | 31 - src/serena/resources/dashboard/index.html | 4 +- src/serena/task_executor.py | 12 - test/serena/test_analytics.py | 106 +- test/serena/test_dashboard.py | 3 - test/serena/test_dashboard_code.py | 128 +- uv.lock | 2 +- 82 files changed, 9404 insertions(+), 909 deletions(-) create mode 100644 dashboard/src/components/common/Icon.svelte create mode 100644 dashboard/src/components/common/Popover.svelte delete mode 100644 dashboard/src/components/overview/CancelledExecutions.svelte delete mode 100644 dashboard/src/components/overview/ExecutionsQueue.svelte delete mode 100644 dashboard/src/components/overview/LastExecution.svelte create mode 100644 dashboard/src/lib/fileIcons.ts create mode 100644 dashboard/src/lib/stores/clock.svelte.ts create mode 100644 dashboard/src/lib/symbolTree.ts create mode 100644 dashboard/src/lib/timelineRows.ts create mode 100644 dashboard/tests/code-page.test.ts create mode 100644 dashboard/tests/diagnostics-panel.test.ts delete mode 100644 dashboard/tests/executions-queue.test.ts create mode 100644 dashboard/tests/file-icons.test.ts create mode 100644 dashboard/tests/file-symbols.test.ts create mode 100644 dashboard/tests/file-tree.test.ts create mode 100644 dashboard/tests/icon.test.ts delete mode 100644 dashboard/tests/last-execution.test.ts create mode 100644 dashboard/tests/popover.test.ts create mode 100644 dashboard/tests/symbol-tree.test.ts create mode 100644 dashboard/tests/timeline-row.test.ts create mode 100644 dashboard/tests/timeline-rows.test.ts create mode 100644 docs/superpowers/plans/2026-05-29-code-tab-file-icons-explainer.md create mode 100644 docs/superpowers/plans/2026-05-29-code-tab-visual-redesign.md create mode 100644 docs/superpowers/plans/2026-05-29-scoped-diagnostics.md create mode 100644 docs/superpowers/specs/2026-05-29-code-tab-file-icons-explainer-design.md create mode 100644 docs/superpowers/specs/2026-05-29-code-tab-visual-redesign-design.md create mode 100644 docs/superpowers/specs/2026-05-29-scoped-diagnostics-design.md create mode 100644 src/serena/resources/dashboard/assets/index-CtWjMwlp.css create mode 100644 src/serena/resources/dashboard/assets/index-DENnORmv.js delete mode 100644 src/serena/resources/dashboard/assets/index-i8LjQRS6.css delete mode 100644 src/serena/resources/dashboard/assets/index-vCsKzPK1.js diff --git a/.gitignore b/.gitignore index a32e5cc52..03ccf3a0b 100644 --- a/.gitignore +++ b/.gitignore @@ -278,3 +278,6 @@ zz-misc/ vue-implementation/ news/news.json + +# Playwright MCP session artifacts (screenshots, console logs, page snapshots) +.playwright-mcp/ diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 0c83729be..e6486150a 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -1,13 +1,14 @@ { "name": "serena-dashboard", - "version": "0.0.0", + "version": "0.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "serena-dashboard", - "version": "0.0.0", + "version": "0.2.1", "dependencies": { + "@lucide/svelte": "^1.17.0", "chart.js": "^4.5.1", "chartjs-plugin-datalabels": "^2.2.0" }, @@ -856,7 +857,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -867,7 +867,6 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -878,7 +877,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -888,14 +886,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -908,6 +904,15 @@ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", "license": "MIT" }, + "node_modules/@lucide/svelte": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.17.0.tgz", + "integrity": "sha512-q06YCFBN5CO8cd1ADmLCxWRVMVb7xxvHzqC0lvNoxGa+FLW6Cd1Y1AOxgbQk4Iwe68vkAMCRveNHint4WoaVKg==", + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", @@ -1262,7 +1267,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.10.tgz", "integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==", - "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^8.9.0" @@ -1423,7 +1427,6 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -1437,7 +1440,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/parser": { @@ -1526,7 +1528,7 @@ "version": "8.60.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1724,7 +1726,6 @@ "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1834,7 +1835,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -1977,7 +1977,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2169,7 +2168,6 @@ "version": "5.8.1", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", - "dev": true, "license": "MIT" }, "node_modules/dom-accessibility-api": { @@ -2463,7 +2461,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, "license": "MIT" }, "node_modules/espree": { @@ -2501,7 +2498,6 @@ "version": "2.2.9", "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.9.tgz", "integrity": "sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -2965,7 +2961,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.6" @@ -3115,7 +3110,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -3169,7 +3163,6 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -3863,7 +3856,6 @@ "version": "5.55.9", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.9.tgz", "integrity": "sha512-fTjjT8cHLDwigcu2j3pv7Jq04LklXevPB8uBgyHNiTXv+RMNvVnrjS4UEYrLMkhuq1vpCodHjiW+z/95SDs/fg==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.4", @@ -3991,7 +3983,6 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -5519,7 +5510,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "dev": true, "license": "MIT" } } diff --git a/dashboard/package.json b/dashboard/package.json index c3ae0f790..23346d3e4 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -1,6 +1,6 @@ { "name": "serena-dashboard", - "version": "0.0.0", + "version": "0.2.1", "private": true, "type": "module", "scripts": { @@ -32,6 +32,7 @@ "vitest": "^2.1.0" }, "dependencies": { + "@lucide/svelte": "^1.17.0", "chart.js": "^4.5.1", "chartjs-plugin-datalabels": "^2.2.0" } diff --git a/dashboard/src/App.svelte b/dashboard/src/App.svelte index 02b21bb10..70d9f7172 100644 --- a/dashboard/src/App.svelte +++ b/dashboard/src/App.svelte @@ -30,14 +30,12 @@ const configPoller = createPoller(() => safe(config.poll()), 1000); const queuedPoller = createPoller(() => safe(executions.pollQueued()), 1000); - const lastPoller = createPoller(() => safe(executions.pollLast()), 1000); const logsPoller = createPoller(() => safe(logs.poll()), 1000); const timelinePoller = createPoller(() => safe(timeline.poll()), 1000); const pollers: Record = { config: configPoller, queued: queuedPoller, - last: lastPoller, logs: logsPoller, timeline: timelinePoller, }; diff --git a/dashboard/src/components/code/CodePage.svelte b/dashboard/src/components/code/CodePage.svelte index dabd4be96..cd2c3ffb5 100644 --- a/dashboard/src/components/code/CodePage.svelte +++ b/dashboard/src/components/code/CodePage.svelte @@ -4,50 +4,139 @@ import FileSymbols from './FileSymbols.svelte'; import WorkspaceSearch from './WorkspaceSearch.svelte'; import DiagnosticsPanel from './DiagnosticsPanel.svelte'; + import Popover from '../common/Popover.svelte'; + import { CircleHelp } from '@lucide/svelte'; + + // Badge shows the number of files with diagnostics (spec §"Diagnostics tab") + // — not the raw diagnostic count, which can be thousands (LSP noise on + // lockfiles/markdown) and would clutter the tab. + const diagFileCount = $derived(code.diag_files.length); -
                                    - -
                                    - - {#if code.middle_pane === 'symbols'} - - {:else} - - {/if} -
                                    - + {#snippet children()} +
                                    +
                                    File tree
                                    +
                                    + Project files & folders from disk (respects project ignores). Hover a row to run + diagnostics on that file or folder. +
                                    +
                                    Symbols (file)
                                    +
                                    + Outline of the open file via the LSP textDocument/documentSymbol request. + Click a symbol to copy its path:line. +
                                    +
                                    Search (workspace)
                                    +
                                    + Symbol search across the whole project via the LSP workspace/symbol + request. +
                                    +
                                    Diagnostics
                                    +
                                    + Errors & warnings via the LSP textDocument/diagnostic request — a fresh + pull for a single file, or published/push diagnostics for a directory or project scan. Scope + it to the project, a file, or a directory. Computing is slow and briefly pauses other LSP + tools — run it only when needed. +
                                    +
                                    + {/snippet} + +
                                    + + +
                                    + +
                                    + + {#if code.middle_pane === 'symbols'} + + {:else if code.middle_pane === 'search'} + + {:else} + + {/if} +
                                    +
                                    diff --git a/dashboard/src/components/code/DiagnosticsPanel.svelte b/dashboard/src/components/code/DiagnosticsPanel.svelte index 050466310..8fdc90590 100644 --- a/dashboard/src/components/code/DiagnosticsPanel.svelte +++ b/dashboard/src/components/code/DiagnosticsPanel.svelte @@ -1,26 +1,169 @@
                                    -

                                    Diagnostics

                                    +

                                    {scopeLabel}

                                    - {#if code.diag_loading} -
                                    - ⚠ Computing diagnostics — this can take a while and temporarily slows other LSP-backed tools. + + + {#if resultsStale && code.diag_last_scope} +
                                    + Showing results for {scopePathLabel(code.diag_last_scope)}. Click Refresh to update.
                                    {/if} + {#if scope.kind !== 'file'} + + {/if} + + + {#if code.diag_error}
                                    Diagnostics failed. @@ -30,19 +173,29 @@ {#if code.diag_truncated && !code.diag_loading}
                                    - Showing first {code.diag_files.length} files; project has more. + Showing first {code.diag_files.length} files; + {code.diag_last_scope?.kind === 'directory' + ? 'directory' + : code.diag_last_scope?.kind === 'file' + ? 'this file' + : 'project'} has more.
                                    {/if} {#if code.diag_files.length === 0 && !code.diag_loading && !code.diag_error} -

                                    - Click Refresh to compute diagnostics. (Results are not auto-refreshed because diagnostics is - slow.) -

                                    + {#if code.diag_last_scope} +

                                    + No diagnostics in {code.diag_last_scope.kind === 'project' + ? 'this project' + : (code.diag_last_scope.path ?? 'this project')}. +

                                    + {:else} +

                                    Click Refresh to compute diagnostics.

                                    + {/if} {/if} - {#each code.diag_files as f (f.path)} -
                                    + {#each visibleFiles as f (f.path)} +
                                    {f.path} {f.diagnostics.length} @@ -79,6 +232,9 @@ } .refresh { margin-left: auto; + display: inline-flex; + align-items: center; + gap: var(--space-1); background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius); @@ -90,7 +246,19 @@ opacity: 0.6; cursor: progress; } - .warn { + .spin.on { + animation: spin 1.2s linear infinite; + display: inline-flex; + } + @keyframes spin { + to { + transform: rotate(360deg); + } + } + .slow-warning { + display: flex; + align-items: flex-start; + gap: var(--space-2); background: var(--bg); border: 1px solid var(--border); border-left: 3px solid var(--accent); @@ -98,15 +266,75 @@ padding: var(--space-2); color: var(--text-secondary); margin-bottom: var(--space-2); + font-size: 0.85em; + } + .chips { + display: flex; + flex-wrap: wrap; + gap: var(--space-1); + margin-bottom: var(--space-2); + } + .chip { + display: inline-flex; + align-items: center; + gap: var(--space-1); + background: var(--bg); + border: 1px solid var(--border); + border-radius: 999px; + padding: 2px var(--space-2); + color: var(--chip-color, var(--text-secondary)); + font-size: 0.8em; + cursor: pointer; + } + .chip.active { + background: color-mix(in srgb, var(--chip-color) 12%, transparent); + border-color: var(--chip-color, var(--border)); + } + .scope { + display: flex; + gap: var(--space-1); + margin-bottom: var(--space-2); + } + .scope-btn { + flex: 1; + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: var(--space-1) var(--space-2); + color: var(--text-secondary); + font-size: 0.8em; + cursor: pointer; + } + .scope-btn.active { + background: color-mix(in srgb, var(--accent) 12%, transparent); + border-color: var(--accent); + color: var(--text-primary); + } + .scope-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + } + .stale { + background: color-mix(in srgb, var(--accent) 8%, transparent); + border: 1px solid var(--accent); + border-radius: var(--radius); + padding: var(--space-1) var(--space-2); + margin-bottom: var(--space-2); + color: var(--text-secondary); + font-size: 0.85em; } + .warn, .error-card { background: var(--bg); - border: 1px solid var(--log-error); + border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-2); - color: var(--log-error); margin-bottom: var(--space-2); } + .error-card { + border-color: var(--log-error); + color: var(--log-error); + } .error-card .msg { color: var(--text-primary); font-family: var(--font-mono); diff --git a/dashboard/src/components/code/FileSymbols.svelte b/dashboard/src/components/code/FileSymbols.svelte index 09b1417ec..60bb8f3a9 100644 --- a/dashboard/src/components/code/FileSymbols.svelte +++ b/dashboard/src/components/code/FileSymbols.svelte @@ -1,30 +1,84 @@ -{#snippet symbolNode(s: FileSymbol, depth: number)} -
                                  • - {s.kind} - {s.name} - {s.range.start.line + 1}:{s.range.start.character + 1} -
                                  • - {#if s.children && s.children.length > 0} - {#each s.children as c (c.name + ':' + c.range.start.line)} - {@render symbolNode(c, depth + 1)} - {/each} - {/if} -{/snippet} + function onFilterInput(e: Event) { + const value = (e.target as HTMLInputElement).value; + const p = path; + if (!p) return; + // Debounced 100ms (spec §"Filter input") — re-filtering walks the whole tree. + if (filterTimer) clearTimeout(filterTimer); + filterTimer = setTimeout(() => code.setSymbolFilter(p, value), 100); + } -
                                    + function clickBreadcrumb(idx: number) { + if (!path) return; + const target = pathParts.slice(0, idx + 1).join('/'); + if (idx < pathParts.length - 1) { + // Expand-only: a breadcrumb click reveals the directory, never collapses it. + code.expandPath(target); + } + } + + async function copyLoc(row: DisplayRow) { + if (!path) return; + const ref = `${path}:${row.symbol.range.start.line + 1}`; + try { + await navigator.clipboard.writeText(ref); + copiedKey = row.key; + setTimeout(() => { + if (copiedKey === row.key) copiedKey = null; + }, 1000); + } catch { + /* clipboard unavailable — silently ignore */ + } + } + + function onRowChevron(row: DisplayRow) { + if (!path || !row.hasChildren) return; + code.toggleSymbolExpand(path, row.key); + } + + +
                                    {#if !path}

                                    Select a file from the tree.

                                    {:else if error !== undefined} @@ -35,14 +89,103 @@
                                    {:else if symbols === undefined}

                                    Loading…

                                    - {:else if symbols.length === 0} -

                                    No symbols.

                                    {:else} -
                                      - {#each symbols as s (s.name + ':' + s.range.start.line)} - {@render symbolNode(s, 0)} - {/each} -
                                    +
                                    + +
                                    + {#each KIND_ORDER.filter((k) => counts[k]) as k, i (k)} + {#if i > 0}·{/if} + {pluralizeKind(k, counts[k])} + {/each} + {#if Object.keys(counts).length === 0} + No symbols. + {/if} +
                                    +
                                    + + + +
                                    +
                                    + + {#if rows.length === 0 && symbols.length > 0} +

                                    No matches.

                                    + {:else if symbols.length === 0} +

                                    No symbols.

                                    + {:else} +
                                      + {#each rows as row (row.key)} + {@const meta = getKindMeta(row.symbol.kind)} +
                                    • + {#if row.hasChildren} + + {:else} + + {/if} + + + + {row.symbol.name} + +
                                    • + {/each} +
                                    + {/if} {/if}
                                    @@ -50,11 +193,110 @@ .root { height: 100%; overflow-y: auto; + display: flex; + flex-direction: column; } .empty { color: var(--text-secondary); padding: var(--space-3); } + .bar { + position: sticky; + top: 0; + z-index: 2; + background: var(--bg-elevated); + border-bottom: 1px solid var(--border); + padding: var(--space-2); + display: grid; + grid-template-columns: 1fr auto; + grid-template-areas: + 'crumb controls' + 'counts counts'; + gap: var(--space-1) var(--space-2); + } + .breadcrumb { + grid-area: crumb; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-1); + font-family: var(--font-mono); + font-size: 0.85em; + } + .crumb { + background: transparent; + border: 0; + color: var(--text-secondary); + cursor: pointer; + padding: 0; + } + .crumb:hover { + color: var(--text-primary); + text-decoration: underline; + } + .crumb.leaf { + color: var(--text-primary); + font-weight: 600; + cursor: default; + } + .sep { + color: var(--text-muted); + } + .counts { + grid-area: counts; + color: var(--text-secondary); + font-size: 0.8em; + display: flex; + align-items: center; + gap: var(--space-1); + } + .dot { + color: var(--text-muted); + } + .muted { + color: var(--text-muted); + } + .controls { + grid-area: controls; + display: flex; + align-items: center; + gap: var(--space-1); + } + .filter { + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: 0 var(--space-2); + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-secondary); + } + .filter input { + background: transparent; + border: 0; + padding: var(--space-1); + color: var(--text-primary); + font-family: inherit; + font-size: 0.85em; + outline: none; + width: 140px; + } + .icon-btn { + background: transparent; + border: 1px solid transparent; + border-radius: var(--radius-sm); + padding: var(--space-1); + color: var(--text-secondary); + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + } + .icon-btn:hover { + color: var(--text-primary); + background: var(--bg); + } .list { list-style: none; margin: 0; @@ -64,18 +306,91 @@ } .row { display: grid; - grid-template-columns: 80px 1fr 60px; + grid-template-columns: 18px 22px 1fr auto; + align-items: center; gap: var(--space-2); - padding: var(--space-1) var(--space-2); + padding: 2px var(--space-2); padding-left: calc(var(--space-2) + var(--depth, 0) * var(--space-3)); - border-bottom: 1px solid var(--border); + color: var(--text-primary); + position: relative; + } + .row:hover { + background: var(--bg); + } + /* Subtle indent guide: a 1px column drawn at each depth. + Depth 0 has no guide (no parent to draw under). */ + .row::before { + content: ''; + position: absolute; + left: calc(var(--space-2) + var(--depth, 0) * var(--space-3) - var(--space-1)); + top: 0; + bottom: 0; + width: 1px; + background: var(--border); + opacity: 0.6; } - .kind { + .row.depth-0::before { + opacity: 0; + } + .chev { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + background: transparent; + border: 0; color: var(--text-secondary); + cursor: pointer; + } + span.chev { + cursor: default; + } + .badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + border-radius: 6px; + background: color-mix(in srgb, var(--kind-color) 18%, transparent); + color: var(--kind-color); + } + .name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .loc { - color: var(--text-secondary); - text-align: right; + background: transparent; + border: 0; + color: var(--text-muted); + font-family: inherit; + font-size: 0.95em; + cursor: pointer; + font-variant-numeric: tabular-nums; + display: inline-flex; + align-items: center; + gap: var(--space-1); + } + .loc:hover { + color: var(--text-primary); + } + .row:hover .loc :global(.copy-icon) { + opacity: 1; + } + .loc :global(.copy-icon) { + opacity: 0; + transition: opacity 0.12s; + } + /* Sticky depth-0 parents only — nested parents shouldn't stack at top:0. + Pin BELOW the sticky header bar (--bar-h), otherwise the opaque, higher + z-index bar (which also sits at top:0) hides the pinned parent entirely. */ + .row.has-children.depth-0 { + position: sticky; + top: var(--bar-h, 0px); + background: var(--bg-elevated); + z-index: 1; } .error-card { background: var(--bg); diff --git a/dashboard/src/components/code/FileTree.svelte b/dashboard/src/components/code/FileTree.svelte index 31c9f698c..ef79a0a78 100644 --- a/dashboard/src/components/code/FileTree.svelte +++ b/dashboard/src/components/code/FileTree.svelte @@ -1,5 +1,9 @@
                                    - + {#if code.search_error}
                                    Search failed. @@ -58,18 +57,27 @@ height: 100%; padding: var(--space-2); } - .input { - width: 100%; + .input-wrap { + display: flex; + align-items: center; + gap: var(--space-2); padding: var(--space-2); background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius); + color: var(--text-secondary); + } + .input-wrap:focus-within { + border-color: var(--accent); color: var(--text-primary); - font-family: inherit; } - .input:focus { + .input-wrap input { + flex: 1; + background: transparent; + border: 0; outline: none; - border-color: var(--accent); + color: var(--text-primary); + font-family: inherit; } .status { color: var(--text-secondary); diff --git a/dashboard/src/components/common/Collapsible.svelte b/dashboard/src/components/common/Collapsible.svelte index 8255f66ca..9ed18540b 100644 --- a/dashboard/src/components/common/Collapsible.svelte +++ b/dashboard/src/components/common/Collapsible.svelte @@ -1,5 +1,7 @@ + +{#if label} + +{:else} +
                                    {/each} diff --git a/dashboard/src/components/overview/ExecutionsQueue.svelte b/dashboard/src/components/overview/ExecutionsQueue.svelte deleted file mode 100644 index a59eca369..000000000 --- a/dashboard/src/components/overview/ExecutionsQueue.svelte +++ /dev/null @@ -1,74 +0,0 @@ - - -{#if visible.length} -
                                    - {#each visible as ex (ex.task_id)} -
                                    - {#if ex.is_running}{/if} - {ex.name} - -
                                    - {/each} -
                                    -{:else} -
                                    No queued executions.
                                    -{/if} -{#if cancelError}{/if} - - diff --git a/dashboard/src/components/overview/LastExecution.svelte b/dashboard/src/components/overview/LastExecution.svelte deleted file mode 100644 index 5ee33189a..000000000 --- a/dashboard/src/components/overview/LastExecution.svelte +++ /dev/null @@ -1,94 +0,0 @@ - - -{#if execution} -
                                    - -
                                    - {statusWord} - {execution.name} -
                                    - #{execution.task_id} -
                                    -{:else} -
                                    None yet.
                                    -{/if} - - diff --git a/dashboard/src/components/overview/OverviewPage.svelte b/dashboard/src/components/overview/OverviewPage.svelte index b9f37a047..3de03fdec 100644 --- a/dashboard/src/components/overview/OverviewPage.svelte +++ b/dashboard/src/components/overview/OverviewPage.svelte @@ -7,9 +7,6 @@ import ListPanel from './ListPanel.svelte'; import ProjectsPanel from './ProjectsPanel.svelte'; import Spinner from '../common/Spinner.svelte'; - import ExecutionsQueue from './ExecutionsQueue.svelte'; - import LastExecution from './LastExecution.svelte'; - import CancelledExecutions from './CancelledExecutions.svelte'; import NewsSection from './NewsSection.svelte'; import SummaryCards from './SummaryCards.svelte'; import Timeline from './Timeline.svelte'; @@ -49,7 +46,6 @@ {:else} -
                                    @@ -67,21 +63,7 @@ - - - - {#if executions.cancelled.length} - - - - {/if} - - - +
                                    diff --git a/dashboard/src/components/overview/ProjectsPanel.svelte b/dashboard/src/components/overview/ProjectsPanel.svelte index c9c4fbf60..06e9972d3 100644 --- a/dashboard/src/components/overview/ProjectsPanel.svelte +++ b/dashboard/src/components/overview/ProjectsPanel.svelte @@ -6,7 +6,7 @@ - + {#if projects.length}
                                      {#each projects as p (p.path)} diff --git a/dashboard/src/components/overview/Timeline.svelte b/dashboard/src/components/overview/Timeline.svelte index 18c366ef2..8a0891afb 100644 --- a/dashboard/src/components/overview/Timeline.svelte +++ b/dashboard/src/components/overview/Timeline.svelte @@ -1,21 +1,63 @@
                                      @@ -32,14 +74,41 @@ type="button" class="ctrl" aria-label={store.paused ? 'Resume' : 'Pause'} - onclick={() => store.togglePause()}>{store.paused ? '▶ Resume' : '⏸ Pause'} store.togglePause()} + > + {store.paused ? 'Resume' : 'Pause'}
                                    +
                                    + {#each ALL_STATUSES as s (s)} + + {/each} +
                                    + + {#if executions.cancelError} + + {/if} {#if store.pausedGap}
                                      - {#each visible as r (r.seq)} - + {#each visible as r, i (r.key)} + {/each} {#if visible.length === 0} -
                                    • No calls yet.
                                    • +
                                    • No calls match the current filter.
                                    • {/if}
                                    @@ -125,6 +198,38 @@ .ctrl:hover { background: var(--bg-secondary-btn); } + .status-filter { + display: flex; + flex-wrap: wrap; + gap: var(--space-1); + margin-bottom: var(--space-2); + } + .chip { + background: transparent; + border: 1px solid var(--border); + color: var(--text-muted); + border-radius: 999px; + padding: 2px 10px; + cursor: pointer; + font-family: inherit; + font-size: 0.85em; + } + .chip:hover { + background: var(--bg-secondary-btn); + } + .chip.active { + color: var(--text-primary); + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 12%, transparent); + } + .chip.success.active { + border-color: var(--success); + background: color-mix(in srgb, var(--success) 12%, transparent); + } + .chip.fail.active { + border-color: var(--log-error); + background: color-mix(in srgb, var(--log-error) 12%, transparent); + } .pager { display: flex; gap: var(--space-2); @@ -162,6 +267,9 @@ text-align: center; } .banner { + display: flex; + align-items: center; + gap: var(--space-2); background: var(--bg-secondary-btn); border: 1px solid var(--border); border-radius: var(--radius-sm); @@ -173,4 +281,13 @@ border-color: var(--log-error); color: var(--log-error); } + .link { + background: transparent; + border: 0; + color: inherit; + cursor: pointer; + margin-left: auto; + padding: 0 var(--space-2); + font-size: 1.1em; + } diff --git a/dashboard/src/components/overview/TimelineRow.svelte b/dashboard/src/components/overview/TimelineRow.svelte index ad98f1fb2..7389883be 100644 --- a/dashboard/src/components/overview/TimelineRow.svelte +++ b/dashboard/src/components/overview/TimelineRow.svelte @@ -1,71 +1,229 @@ -
                                  • - - {#if expanded} -
                                    -
                                    seq: {record.seq}
                                    -
                                    started: {new Date(record.started_at * 1000).toISOString()}
                                    -
                                    duration: {formatDurationMs(record.duration_ms)}
                                    - {#if record.error_message} -
                                    error: {record.error_message}
                                    +
                                  • +
                                    + - {/if} - {#if record.input_truncated} -
                                    (server-truncated at 8 KB)
                                    + {durationLabel} + + + + + {#if row.kind === 'live' && row.status === 'running' && oncancel} + + {/if} +
                                    + {#if expanded} + {#if row.kind === 'history'} + {@const record = row.record} +
                                    +
                                    seq: {record.seq}
                                    +
                                    + started: + {new Date(record.started_at * 1000).toISOString()} +
                                    +
                                    duration: {formatDurationMs(record.duration_ms)}
                                    +
                                    + tokens: + ↓ {formatNumber(record.input_tokens)} in + · + ↑ {formatNumber(record.output_tokens)} out +
                                    + {#if record.error_message} +
                                    error: {record.error_message}
                                    {/if} +
                                    +
                                    + input: + +
                                    +
                                    {showFullInput || fmtInput.length < 400
                                    +              ? fmtInput
                                    +              : fmtInput.slice(0, 400) + '…'}
                                    + {#if fmtInput.length >= 400} + + {/if} + {#if record.input_truncated} +
                                    (server-truncated at 8 KB)
                                    + {/if} +
                                    +
                                    +
                                    + output: + +
                                    +
                                    {showFullOutput || fmtOutput.length < 400
                                    +              ? fmtOutput
                                    +              : fmtOutput.slice(0, 400) + '…'}
                                    + {#if fmtOutput.length >= 400} + + {/if} + {#if record.output_truncated} +
                                    (server-truncated at 8 KB)
                                    + {/if} +
                                    -
                                    -
                                    output:
                                    -
                                    {showFullOutput || record.output_preview.length < 400
                                    -            ? record.output_preview
                                    -            : record.output_preview.slice(0, 400) + '…'}
                                    - {#if record.output_preview.length >= 400} - + {:else} + {@const exec = row.execution} +
                                    +
                                    task id: #{exec.task_id}
                                    +
                                    + name: + {exec.display_name || exec.name} +
                                    + {#if exec.started_at} +
                                    + started: + {new Date(exec.started_at * 1000).toISOString()} +
                                    + {:else} +
                                    started: (queued, not yet running)
                                    + {/if} + {#if exec.finished_at} +
                                    + finished: + {new Date(exec.finished_at * 1000).toISOString()} +
                                    {/if} - {#if record.output_truncated} -
                                    (server-truncated at 8 KB)
                                    + {#if exec.error_message} +
                                    error: {exec.error_message}
                                    {/if}
                                    -
                                    + {/if} {/if}
                                  • @@ -74,10 +232,18 @@ border-bottom: 1px solid var(--border); list-style: none; } + .row.latest { + background: color-mix(in srgb, var(--accent) 6%, transparent); + border-left: 3px solid var(--accent); + } + .head-wrap { + display: flex; + align-items: center; + } .head { - width: 100%; + flex: 1; display: grid; - grid-template-columns: 24px 110px 1fr 70px 50px; + grid-template-columns: 20px auto 1fr auto 64px 22px; gap: var(--space-2); align-items: center; background: transparent; @@ -96,22 +262,86 @@ color: var(--text-muted); } .time { + display: flex; + align-items: baseline; + gap: var(--space-1); + white-space: nowrap; + } + .time .abs { + color: var(--text-muted); + } + .time .rel { color: var(--text-muted); + opacity: 0.7; + font-size: 0.85em; } .tool { color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .tokens { + display: inline-flex; + gap: var(--space-2); + color: var(--text-muted); + white-space: nowrap; + font-variant-numeric: tabular-nums; + } + .tok-in { + color: var(--accent); + } + .tok-out { + color: var(--success); + } + .tok-sep { + color: var(--text-muted); } .duration { color: var(--text-muted); + text-align: right; + font-variant-numeric: tabular-nums; } - .status.ok { + .status { + display: inline-flex; + align-items: center; + justify-content: center; + } + .status.success { color: var(--success); } - .status.err { + .status.fail { color: var(--log-error); } + .status.running { + color: var(--accent); + } + .status.cancelled { + color: var(--text-muted); + } + .status :global(.spin) { + animation: spin 1s linear infinite; + } + @keyframes spin { + to { + transform: rotate(360deg); + } + } + .cancel { + border: none; + background: none; + cursor: pointer; + color: var(--log-error); + font-weight: bold; + font-size: 16px; + line-height: 1; + padding: 0 var(--space-2); + } + .cancel:hover { + background: var(--bg-secondary-btn); + } .detail { - padding: var(--space-2) var(--space-3) var(--space-3) calc(24px + var(--space-2)); + padding: var(--space-2) var(--space-3) var(--space-3) calc(20px + var(--space-2)); font-family: var(--font-mono); font-size: 0.85em; color: var(--text-primary); @@ -123,6 +353,28 @@ .block { margin-top: var(--space-2); } + .block-head { + display: flex; + align-items: center; + gap: var(--space-2); + } + .copy { + display: inline-flex; + align-items: center; + gap: 4px; + background: transparent; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--text-muted); + cursor: pointer; + font-family: inherit; + font-size: 0.9em; + padding: 1px var(--space-1); + } + .copy:hover { + background: var(--bg-secondary-btn); + color: var(--text-primary); + } .code { background: var(--bg-secondary-btn); border: 1px solid var(--border); diff --git a/dashboard/src/components/shell/Header.svelte b/dashboard/src/components/shell/Header.svelte index 10d5495df..88a312531 100644 --- a/dashboard/src/components/shell/Header.svelte +++ b/dashboard/src/components/shell/Header.svelte @@ -2,6 +2,8 @@ import { theme } from '$lib/stores/theme.svelte'; import ThemeToggle from './ThemeToggle.svelte'; import BannerCarousel from '../banners/BannerCarousel.svelte'; + import Icon from '../common/Icon.svelte'; + import { Power } from '@lucide/svelte'; import type { View } from '$lib/pollers'; let { active, @@ -28,7 +30,7 @@ title="Shutdown Server" onclick={() => onshutdown()} > - +
                                    @@ -39,6 +41,13 @@ aria-current={active === 'overview' ? 'page' : undefined} onclick={() => onnavigate('overview')}>Overview + -
                                    diff --git a/dashboard/src/components/shell/ThemeToggle.svelte b/dashboard/src/components/shell/ThemeToggle.svelte index e843b0c5a..17a7921e5 100644 --- a/dashboard/src/components/shell/ThemeToggle.svelte +++ b/dashboard/src/components/shell/ThemeToggle.svelte @@ -1,18 +1,30 @@ diff --git a/dashboard/src/components/stats/DrillDownPanel.svelte b/dashboard/src/components/stats/DrillDownPanel.svelte index 308f7a9d7..ba8673a9f 100644 --- a/dashboard/src/components/stats/DrillDownPanel.svelte +++ b/dashboard/src/components/stats/DrillDownPanel.svelte @@ -2,6 +2,8 @@ import { stats } from '$lib/stores/stats.svelte'; import { timeline } from '$lib/stores/timeline.svelte'; import { formatDurationMs, formatNumber, percentile } from '$lib/format'; + import Icon from '../common/Icon.svelte'; + import { X, ArrowRight, TriangleAlert } from '@lucide/svelte'; interface Props { onOpenInTimeline?: (_tool: string) => void; @@ -16,6 +18,8 @@ const p95 = $derived(percentile(durations, 95)); const recentErrors = $derived(recordsForTool.filter((r) => !r.success).slice(0, 5)); const last20 = $derived(recordsForTool.slice(0, 20)); + // Largest duration in the visible slice — scales the per-call latency bars. + const maxVisible = $derived(Math.max(1, ...last20.map((r) => r.duration_ms))); const errorRate = $derived( entry && entry.num_times_called > 0 @@ -27,63 +31,101 @@ ? (entry.total_duration_ms ?? 0) / entry.num_times_called : 0, ); + const hasErrors = $derived((entry?.num_errors ?? 0) > 0); + + function hhmmss(epochSec: number): string { + return new Date(epochSec * 1000).toISOString().slice(11, 19); + } {#if tool}