From e01e5045029e6d1c563a211ccaadd1b344fb43ff Mon Sep 17 00:00:00 2001 From: Alva Chien Date: Sun, 30 Aug 2026 20:15:07 +0800 Subject: [PATCH 1/4] chore: upgrade to Angular 22.1 and prune stale dependency overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Angular 21.2 → 22.1.x, TypeScript ~6.0, angular-auth-oidc-client 22, angular-eslint 22 (recommended template configs now ship in the umbrella `angular-eslint` package) - Adapt code and specs to the new toolchain (Eager changeDetection on AppComponent, withXhr() for HttpClient, tsconfig extendedDiagnostics suppressions, spec updates) - Drop the Angular 21-era `overrides` block: every floor is now met by plain resolution, four entries match no package at all, and @babel/core ^7.29.7 was silently downgrading Angular 22's exact 8.0.1 pin - Remove @types/dompurify (deps) and @types/marked (devDeps): dompurify 3.4 and marked 17 bundle their own types, so the stubs were shadowed and unused - Bump version to 1.9.1 (package.json + both environment files) Co-Authored-By: Claude Code --- CLAUDE.md | 2 +- README.md | 2 +- docs/vocabulary-exercises-architecture.md | 2 +- eslint.config.js | 9 +- package-lock.json | 7502 +++++++++-------- package.json | 60 +- src/app/app.component.spec.ts | 3 +- src/app/app.component.ts | 3 +- src/app/app.config.ts | 4 +- ...-exercises-select-dialog.component.spec.ts | 6 +- .../english-listening.component.spec.ts | 4 +- ...-exercises-select-dialog.component.spec.ts | 6 +- ...anslate-exercises-info-dialog.component.ts | 3 +- ...ranslate-exercises-llm-dialog.component.ts | 10 +- ...late-exercises-options-dialog.component.ts | 7 +- ...exercises-printoptions-dialog.component.ts | 3 +- ...lary-exercises-word-list.component.spec.ts | 79 +- src/app/services/ai.service.spec.ts | 4 +- .../audio-service.service.extended.spec.ts | 4 +- .../services/audio-service.service.spec.ts | 12 +- .../services/learning-content.service.spec.ts | 4 +- .../services/learning-rating.service.spec.ts | 4 +- .../filter-dialog.component.spec.ts | 71 +- src/app/shared/footer/footer.ts | 3 +- .../knowledge-exercise-form.component.ts | 14 +- .../knowledge-exercise-item.component.ts | 68 +- .../language-selector.component.ts | 3 +- src/app/shared/logo/logo.ts | 3 +- .../markdown-content.component.spec.ts | 4 +- src/app/shared/mathitem/mathitem.ts | 10 +- .../navigation-focus/navigation-focus.spec.ts | 4 +- .../style-manager/style-manager.spec.ts | 15 +- src/environments/environment.prod.ts | 2 +- src/environments/environment.ts | 2 +- tsconfig.app.json | 10 +- tsconfig.spec.json | 13 +- 36 files changed, 4288 insertions(+), 3667 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 458045c..b4d7a86 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -**alvachien.com** is an Angular 21 AI-powered learning platform for English, Chinese, and Knowledge Bank exercises. Built with Angular CLI 21.1.2. The app is fully standalone (no NgModules for feature code) and uses `@jsverse/transloco` for i18n (English + Chinese). +**alvachien.com** is an Angular 22 AI-powered learning platform for English, Chinese, and Knowledge Bank exercises. Built with Angular CLI 22.1.6 (TypeScript ~6.0). The app is fully standalone (no NgModules for feature code) and uses `@jsverse/transloco` for i18n (English + Chinese). ## Development Commands diff --git a/README.md b/README.md index f4e8266..808f7d5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ An AI-powered web-based Learning app for English, Chinese, and Knowledge Bank. -Built with Angular 21 (standalone components, Signals, Angular Material, Tailwind CSS, Transloco i18n en/zh-CN). This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 21.1.2. +Built with Angular 22 (standalone components, Signals, Angular Material, Tailwind CSS, Transloco i18n en/zh-CN). This project was originally generated with [Angular CLI](https://github.com/angular/angular-cli) version 21.1.2. ### Prerequisites diff --git a/docs/vocabulary-exercises-architecture.md b/docs/vocabulary-exercises-architecture.md index 9fb9a99..f95a2d1 100644 --- a/docs/vocabulary-exercises-architecture.md +++ b/docs/vocabulary-exercises-architecture.md @@ -1,7 +1,7 @@ # Vocabulary Exercises Page — Architecture Design Date: 2026-08-22 -Scope: `src/app/pages/vocabulary-exercises/` — the `/vocabulary` route, an Angular 21 standalone feature page. +Scope: `src/app/pages/vocabulary-exercises/` — the `/vocabulary` route, an Angular 22 standalone feature page. This document describes the **as-built** architecture of the vocabulary exercises page after the `feat/voc-refactor` decomposition. It is the design reference for the page's container/store/screen/dialog structure, the filter pipeline, the rating lifecycle, keyboard handling, and the data models that cross those boundaries — including the **Worksheet** exercise's generation and print-rendering flow (§15), which merges and supersedes the former `vocabulary-print.md` with corrected terminology (the feature is *worksheet* throughout; *print* is reserved for the genuine browser-print mechanics). For the line-by-line review findings and their fix status, see [`vocabulary-page-review.md`](./vocabulary-page-review.md); for the data-model field reference, see [`data-models.md`](./data-models.md). diff --git a/eslint.config.js b/eslint.config.js index 0149ebe..c3dfded 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,6 +1,13 @@ const tseslint = require('typescript-eslint'); const angular = require('@angular-eslint/eslint-plugin'); const angularTemplate = require('@angular-eslint/eslint-plugin-template'); +// angular-eslint v22: the individual plugins no longer ship `configs`; the +// premade recommended configs live in the umbrella `angular-eslint` package. +const { configs: ngConfigs } = require('angular-eslint'); +const templateRecommendedRules = Object.assign( + {}, + ...ngConfigs.templateRecommended.map((config) => config.rules ?? {}) +); module.exports = tseslint.config( { @@ -109,7 +116,7 @@ module.exports = tseslint.config( '@angular-eslint/template': angularTemplate }, rules: { - ...angularTemplate.configs.recommended.rules + ...templateRecommendedRules } } ); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3d13c13..66edc7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,28 +1,27 @@ { "name": "knowledgebuilder", - "version": "1.9.0", + "version": "1.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "knowledgebuilder", - "version": "1.9.0", + "version": "1.9.1", "license": "MIT", "dependencies": { - "@angular/animations": "21.2.21", - "@angular/cdk": "^21.2.14", - "@angular/common": "21.2.21", - "@angular/compiler": "21.2.21", - "@angular/core": "21.2.21", - "@angular/forms": "21.2.21", - "@angular/material": "^21.2.14", - "@angular/material-date-fns-adapter": "^21.2.14", - "@angular/platform-browser": "21.2.21", - "@angular/router": "21.2.21", + "@angular/animations": "22.1.4", + "@angular/cdk": "^22.1.4", + "@angular/common": "22.1.4", + "@angular/compiler": "22.1.4", + "@angular/core": "22.1.4", + "@angular/forms": "22.1.4", + "@angular/material": "^22.1.4", + "@angular/material-date-fns-adapter": "^22.1.4", + "@angular/platform-browser": "22.1.4", + "@angular/router": "22.1.4", "@jsverse/transloco": "^8.2.1", - "@types/dompurify": "^3.0.5", "actslib": "^0.6.81", - "angular-auth-oidc-client": "^21.0.2", + "angular-auth-oidc-client": "^22.0.0", "date-fns": "^4.1.0", "dompurify": "^3.4.11", "howler": "^2.2.4", @@ -35,238 +34,28 @@ "zone.js": "~0.15.0" }, "devDependencies": { - "@angular-eslint/builder": "^21.1.0", - "@angular-eslint/eslint-plugin": "^21.1.0", - "@angular-eslint/eslint-plugin-template": "^21.1.0", - "@angular/build": "21.2.21", - "@angular/cli": "21.2.21", - "@angular/compiler-cli": "21.2.21", - "@angular/platform-browser": "21.2.21", + "@angular-eslint/builder": "^22.1.0", + "@angular-eslint/eslint-plugin": "^22.1.0", + "@angular-eslint/eslint-plugin-template": "^22.1.0", + "@angular/build": "22.1.6", + "@angular/cli": "22.1.6", + "@angular/compiler-cli": "22.1.4", "@types/howler": "^2.2.11", "@types/katex": "^0.16.7", - "@types/marked": "^5.0.2", "@types/node": "^22.13.1", "@typescript-eslint/eslint-plugin": "^8.54.0", "@typescript-eslint/parser": "^8.54.0", + "angular-eslint": "^22.1.0", "eslint": "^9.39.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "^5.5.5", "jsdom": "^26.0.0", "prettier": "^3.8.1", - "typescript": "~5.9.3", + "typescript": "~6.0.3", "typescript-eslint": "^8.54.0", "vitest": "^4.0.0" } }, - "node_modules/@algolia/abtesting": { - "version": "1.14.1", - "resolved": "https://registry.npmmirror.com/@algolia/abtesting/-/abtesting-1.14.1.tgz", - "integrity": "sha512-Dkj0BgPiLAaim9sbQ97UKDFHJE/880wgStAM18U++NaJ/2Cws34J5731ovJifr6E3Pv4T2CqvMXf8qLCC417Ew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-abtesting": { - "version": "5.48.1", - "resolved": "https://registry.npmmirror.com/@algolia/client-abtesting/-/client-abtesting-5.48.1.tgz", - "integrity": "sha512-LV5qCJdj+/m9I+Aj91o+glYszrzd7CX6NgKaYdTOj4+tUYfbS62pwYgUfZprYNayhkQpVFcrW8x8ZlIHpS23Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "5.48.1", - "resolved": "https://registry.npmmirror.com/@algolia/client-analytics/-/client-analytics-5.48.1.tgz", - "integrity": "sha512-/AVoMqHhPm14CcHq7mwB+bUJbfCv+jrxlNvRjXAuO+TQa+V37N8k1b0ijaRBPdmSjULMd8KtJbQyUyabXOu6Kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-common": { - "version": "5.48.1", - "resolved": "https://registry.npmmirror.com/@algolia/client-common/-/client-common-5.48.1.tgz", - "integrity": "sha512-VXO+qu2Ep6ota28ktvBm3sG53wUHS2n7bgLWmce5jTskdlCD0/JrV4tnBm1l7qpla1CeoQb8D7ShFhad+UoSOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-insights": { - "version": "5.48.1", - "resolved": "https://registry.npmmirror.com/@algolia/client-insights/-/client-insights-5.48.1.tgz", - "integrity": "sha512-zl+Qyb0nLg+Y5YvKp1Ij+u9OaPaKg2/EPzTwKNiVyOHnQJlFxmXyUZL1EInczAZsEY8hVpPCLtNfhMhfxluXKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "5.48.1", - "resolved": "https://registry.npmmirror.com/@algolia/client-personalization/-/client-personalization-5.48.1.tgz", - "integrity": "sha512-r89Qf9Oo9mKWQXumRu/1LtvVJAmEDpn8mHZMc485pRfQUMAwSSrsnaw1tQ3sszqzEgAr1c7rw6fjBI+zrAXTOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-query-suggestions": { - "version": "5.48.1", - "resolved": "https://registry.npmmirror.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.48.1.tgz", - "integrity": "sha512-TPKNPKfghKG/bMSc7mQYD9HxHRUkBZA4q1PEmHgICaSeHQscGqL4wBrKkhfPlDV1uYBKW02pbFMUhsOt7p4ZpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-search": { - "version": "5.48.1", - "resolved": "https://registry.npmmirror.com/@algolia/client-search/-/client-search-5.48.1.tgz", - "integrity": "sha512-4Fu7dnzQyQmMFknYwTiN/HxPbH4DyxvQ1m+IxpPp5oslOgz8m6PG5qhiGbqJzH4HiT1I58ecDiCAC716UyVA8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/ingestion": { - "version": "1.48.1", - "resolved": "https://registry.npmmirror.com/@algolia/ingestion/-/ingestion-1.48.1.tgz", - "integrity": "sha512-/RFq3TqtXDUUawwic/A9xylA2P3LDMO8dNhphHAUOU51b1ZLHrmZ6YYJm3df1APz7xLY1aht6okCQf+/vmrV9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/monitoring": { - "version": "1.48.1", - "resolved": "https://registry.npmmirror.com/@algolia/monitoring/-/monitoring-1.48.1.tgz", - "integrity": "sha512-Of0jTeAZRyRhC7XzDSjJef0aBkgRcvRAaw0ooYRlOw57APii7lZdq+layuNdeL72BRq1snaJhoMMwkmLIpJScw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/recommend": { - "version": "5.48.1", - "resolved": "https://registry.npmmirror.com/@algolia/recommend/-/recommend-5.48.1.tgz", - "integrity": "sha512-bE7JcpFXzxF5zHwj/vkl2eiCBvyR1zQ7aoUdO+GDXxGp0DGw7nI0p8Xj6u8VmRQ+RDuPcICFQcCwRIJT5tDJFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "5.48.1", - "resolved": "https://registry.npmmirror.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.48.1.tgz", - "integrity": "sha512-MK3wZ2koLDnvH/AmqIF1EKbJlhRS5j74OZGkLpxI4rYvNi9Jn/C7vb5DytBnQ4KUWts7QsmbdwHkxY5txQHXVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-fetch": { - "version": "5.48.1", - "resolved": "https://registry.npmmirror.com/@algolia/requester-fetch/-/requester-fetch-5.48.1.tgz", - "integrity": "sha512-2oDT43Y5HWRSIQMPQI4tA/W+TN/N2tjggZCUsqQV440kxzzoPGsvv9QP1GhQ4CoDa+yn6ygUsGp6Dr+a9sPPSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-node-http": { - "version": "5.48.1", - "resolved": "https://registry.npmmirror.com/@algolia/requester-node-http/-/requester-node-http-5.48.1.tgz", - "integrity": "sha512-xcaCqbhupVWhuBP1nwbk1XNvwrGljozutEiLx06mvqDf3o8cHyEgQSHS4fKJM+UAggaWVnnFW+Nne5aQ8SUJXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -294,40 +83,40 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.2102.21", - "resolved": "https://registry.npmmirror.com/@angular-devkit/architect/-/architect-0.2102.21.tgz", - "integrity": "sha512-WqITVviALevNpCQoI50e3YY9qNOQT+gqTow/4FhWsxlAJFMb5uyilFpVz2hGYWPgosF3OAKmT1h9r+Jm9S6SUQ==", + "version": "0.2201.6", + "resolved": "https://registry.npmmirror.com/@angular-devkit/architect/-/architect-0.2201.6.tgz", + "integrity": "sha512-oGQEdof2/1bZk58PN9dvpGi8pvtbgE5vkP1xRtCqN8dSVxwre1l0pKynOj/Uge1KxjCvs4c1QrQu0vsAnY0vpg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "21.2.21", + "@angular-devkit/core": "22.1.6", "rxjs": "7.8.2" }, "bin": { "architect": "bin/cli.js" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "node": "^22.22.3 || ^24.15.0 || >=26.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/core": { - "version": "21.2.21", - "resolved": "https://registry.npmmirror.com/@angular-devkit/core/-/core-21.2.21.tgz", - "integrity": "sha512-xOr6mZ00M6hgM/xltAVkPVc/Yd1a/Wa0CGecV64JTITaVaSH8p8+wXe62Xavdr3pcc9cLKKUUB4mAup4k7Kkyw==", + "version": "22.1.6", + "resolved": "https://registry.npmmirror.com/@angular-devkit/core/-/core-22.1.6.tgz", + "integrity": "sha512-KLBsZoc2RhOy0xSdeYcLoSOqV/fBP3feBmhY9o12ry2IuyW/17sCmWr6XbIfTIYQN6M98Y1ewmwVNFsvV5b0gA==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "8.18.0", + "ajv": "8.20.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "picomatch": "4.0.4", + "picomatch": "4.0.5", "rxjs": "7.8.2", "source-map": "0.7.6" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "node": "^22.22.3 || ^24.15.0 || >=26.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, @@ -341,190 +130,229 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "21.2.21", - "resolved": "https://registry.npmmirror.com/@angular-devkit/schematics/-/schematics-21.2.21.tgz", - "integrity": "sha512-LgTd/0CpyWSxMJLLk0X85F5RyDlJekssLlapU1C8fKqOUdWONYncTXYvZUQ+zbRAGXHcSZrst1Vm8qdXbo11Mg==", + "version": "22.1.6", + "resolved": "https://registry.npmmirror.com/@angular-devkit/schematics/-/schematics-22.1.6.tgz", + "integrity": "sha512-IbDO9KbQyQm20uinsfT8d9ZtQw41/WVutI/65mAw39WZfN8Ky+MOgOUJCGLiSccMprvZ9YdodBTnN9bLH149Ag==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "21.2.21", + "@angular-devkit/core": "22.1.6", "jsonc-parser": "3.3.1", - "magic-string": "0.30.21", - "ora": "9.3.0", + "magic-string": "1.0.0", + "ora": "9.4.1", "rxjs": "7.8.2" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "node": "^22.22.3 || ^24.15.0 || >=26.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/schematics/node_modules/magic-string": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-1.0.0.tgz", + "integrity": "sha512-CGvjzMN08iv6w1mm4/x3Gh1hLb4VnyRUA15FFpl6CsCIGGoe36k7kY5KNz9QDbSBN5I/fWHM6ZlIkUTa5xdUEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/@angular-eslint/builder": { - "version": "21.4.0", - "resolved": "https://registry.npmmirror.com/@angular-eslint/builder/-/builder-21.4.0.tgz", - "integrity": "sha512-3kgGmrVaCYbLtDjC8g4BmMBbdz4thsOB8/NYly8JtXM8EuDZEk5Pz6VTRpJR02ARprwayraTTmhyvq6OGBlQ9w==", + "version": "22.1.0", + "resolved": "https://registry.npmmirror.com/@angular-eslint/builder/-/builder-22.1.0.tgz", + "integrity": "sha512-gmdk06PK0whNJlapIQjRnhud2/n+oIIDBIoLUHWFFxhHRs+ZstVc6+81gf+sYDOm1Ehmb0V3nZAg3YpT1Mk0Eg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": ">= 0.2100.0 < 0.2200.0", - "@angular-devkit/core": ">= 21.0.0 < 22.0.0" + "@angular-devkit/architect": ">= 0.2200.0 < 0.2300.0", + "@angular-devkit/core": ">= 22.0.0 < 23.0.0" }, "peerDependencies": { - "@angular/cli": ">= 21.0.0 < 22.0.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "@angular/cli": ">= 22.0.0 < 23.0.0", + "eslint": "^9.0.0 || ^10.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "21.4.0", - "resolved": "https://registry.npmmirror.com/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-21.4.0.tgz", - "integrity": "sha512-/3H4BPbQ1BHJkkrUsfusZtmHc+qiFWBBZ9UDPWah4xZMjflexOK9U4GYeH7nMjcuyqFnIlMMeJJNwNLGt/hmdg==", + "version": "22.1.0", + "resolved": "https://registry.npmmirror.com/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-22.1.0.tgz", + "integrity": "sha512-iOtOQ2jtrtko1rIQo6i+g3ezxGL0lyYv80j4GccFTK1JGh4K+AqYkmaBvfUfNtqoE/7VcKsOoyxaFt6iIpKhaQ==", "dev": true, "license": "MIT" }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "21.4.0", - "resolved": "https://registry.npmmirror.com/@angular-eslint/eslint-plugin/-/eslint-plugin-21.4.0.tgz", - "integrity": "sha512-mow2DMj+xBvGl5t7jzC34R8YfbHbaGNyCNFzpovtl9qc0JbuqLyg6htmt8xb05f8ZjATOr4nz0ESt6HV4c51hw==", + "version": "22.1.0", + "resolved": "https://registry.npmmirror.com/@angular-eslint/eslint-plugin/-/eslint-plugin-22.1.0.tgz", + "integrity": "sha512-nRwdbUiW7vF0gbwtxw2hRGPZYZCNWOTLZKw4HM+I37o5YFIA0CFLtbyRKgZWs6JZCIqzTYSwCyKNsW41Qw0FwQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "21.4.0", - "@angular-eslint/utils": "21.4.0", + "@angular-eslint/bundled-angular-compiler": "22.1.0", + "@angular-eslint/utils": "22.1.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "@typescript-eslint/utils": "^8.0.0", + "eslint": "^9.0.0 || ^10.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "21.4.0", - "resolved": "https://registry.npmmirror.com/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-21.4.0.tgz", - "integrity": "sha512-sJEHx2WYnvOgPpzP1eHnUdRS06zgKmRxbiIR0JiCcaSen5iv1HlsMieXy//FS9TtNW+abHOy4UtDuGuSPflPFA==", + "version": "22.1.0", + "resolved": "https://registry.npmmirror.com/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-22.1.0.tgz", + "integrity": "sha512-GK/Mwhwvj+MX6DefD29/IfuYjcai5CsCbYJ6Qb9P6IAzZaQqw+EN+CiXxIwzMnzm2edLcJzAsX8iBk4X7sVi2Q==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "21.4.0", - "@angular-eslint/utils": "21.4.0", + "@angular-eslint/bundled-angular-compiler": "22.1.0", + "@angular-eslint/utils": "22.1.0", "aria-query": "5.3.2", "axobject-query": "4.1.0" }, "peerDependencies": { - "@angular-eslint/template-parser": "21.4.0", - "@typescript-eslint/types": "^7.11.0 || ^8.0.0", - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "@angular-eslint/template-parser": "22.1.0", + "@typescript-eslint/types": "^8.0.0", + "@typescript-eslint/utils": "^8.0.0", + "eslint": "^9.0.0 || ^10.0.0", "typescript": "*" } }, + "node_modules/@angular-eslint/schematics": { + "version": "22.1.0", + "resolved": "https://registry.npmmirror.com/@angular-eslint/schematics/-/schematics-22.1.0.tgz", + "integrity": "sha512-/Z7MEO9ys9P5w5itM2mpNW4R/QNAVxtDdcU5LVFulSx/lo7zCDx1rZ/tLDuzbN5k5RGQy5ZkdRIs0MoscsGXQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": ">= 22.0.0 < 23.0.0", + "@angular-devkit/schematics": ">= 22.0.0 < 23.0.0", + "@angular-eslint/eslint-plugin": "22.1.0", + "@angular-eslint/eslint-plugin-template": "22.1.0", + "ignore": "7.0.5", + "semver": "7.8.5", + "strip-json-comments": "3.1.1" + }, + "peerDependencies": { + "@angular/cli": ">= 22.0.0 < 23.0.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@angular-eslint/template-parser": { - "version": "21.4.0", - "resolved": "https://registry.npmmirror.com/@angular-eslint/template-parser/-/template-parser-21.4.0.tgz", - "integrity": "sha512-BaUSLSyS+43fzDoJkTMkGqNdCXq3fGnUZsfXTmrlZPJf5AYFbgAlAPGZXDJyoNWw43fux+DafdlrlKcYUSgSIw==", + "version": "22.1.0", + "resolved": "https://registry.npmmirror.com/@angular-eslint/template-parser/-/template-parser-22.1.0.tgz", + "integrity": "sha512-gcufZLI/Rl2fOWtBk2MgMRkH1t+OrbJGxIsfT0w3pVjKm0zwi7njM/dtPbVjCHzsGhx7szQMvY0i0UwTBVYkSw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "21.4.0", + "@angular-eslint/bundled-angular-compiler": "22.1.0", "eslint-scope": "9.1.2" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "eslint": "^9.0.0 || ^10.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/utils": { - "version": "21.4.0", - "resolved": "https://registry.npmmirror.com/@angular-eslint/utils/-/utils-21.4.0.tgz", - "integrity": "sha512-7pi+Ga7QmdH5Ig/diau6fR5L4yubgKr9TOjdCg7OeuE/zo0O3osTCNT6JOodzS/iQM1kSCJFDoIBKFeUOttiNw==", + "version": "22.1.0", + "resolved": "https://registry.npmmirror.com/@angular-eslint/utils/-/utils-22.1.0.tgz", + "integrity": "sha512-zDPJqgOxlkG20UJWsVDSvnEtC2MfIP0+yKMaQUBL+2nrEknV/fVLrz7ApnerWiDrGTcAms5si49KV10MarMpRA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "21.4.0" + "@angular-eslint/bundled-angular-compiler": "22.1.0" }, "peerDependencies": { - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "@typescript-eslint/utils": "^8.0.0", + "eslint": "^9.0.0 || ^10.0.0", "typescript": "*" } }, "node_modules/@angular/animations": { - "version": "21.2.21", - "resolved": "https://registry.npmmirror.com/@angular/animations/-/animations-21.2.21.tgz", - "integrity": "sha512-ywgwBi6V/6WDnVH956q+tMfXoM1VsTUzp0XtUoloMqzDQ0FgJF9tDuBgt8ixHrUUTziZ3oudzgPza2YFBMafLA==", + "version": "22.1.4", + "resolved": "https://registry.npmmirror.com/@angular/animations/-/animations-22.1.4.tgz", + "integrity": "sha512-DZqSHhmSRBVfLlrFxNLQj6PYVhu1w8vj1iUdD6fmlhfGGafuiw/VcUpBgYmy2YNcXmGmwbZsKr1oeJP7fT8zNQ==", "deprecated": "@angular/animations is deprecated. Use `animate.enter` and `animate.leave` instead. For more information see: https://v22.angular.dev/guide/animations.", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" }, "peerDependencies": { - "@angular/core": "21.2.21" + "@angular/core": "22.1.4" } }, "node_modules/@angular/build": { - "version": "21.2.21", - "resolved": "https://registry.npmmirror.com/@angular/build/-/build-21.2.21.tgz", - "integrity": "sha512-Z88uTaPte8uEYW+Sn5BUz7YoGAbi1dcoz8X1eaJi30bT7qtZ5Bph0vELMBu2VCtp9JFgZhsLn+uy8F6iIPiOUQ==", + "version": "22.1.6", + "resolved": "https://registry.npmmirror.com/@angular/build/-/build-22.1.6.tgz", + "integrity": "sha512-J7JBd3hDAV4dlGNMavDC0KjtvbDd4KOddaom1inwkq92tYnD1ljmhDDigAsUBnRvQhJ82cvagD8zi0s9Ki4rTQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2102.21", - "@babel/core": "7.29.7", - "@babel/helper-annotate-as-pure": "7.27.3", + "@angular-devkit/architect": "0.2201.6", + "@babel/core": "8.0.1", + "@babel/helper-annotate-as-pure": "8.0.0", "@babel/helper-split-export-declaration": "7.24.7", - "@inquirer/confirm": "5.1.21", - "@vitejs/plugin-basic-ssl": "2.1.4", - "beasties": "0.4.1", + "@inquirer/confirm": "6.1.1", + "@vitejs/plugin-basic-ssl": "2.3.0", + "beasties": "0.4.3", "browserslist": "^4.26.0", - "esbuild": "0.28.1", - "https-proxy-agent": "7.0.6", - "istanbul-lib-instrument": "6.0.3", + "esbuild": "0.28.2", + "https-proxy-agent": "9.1.0", "jsonc-parser": "3.3.1", - "listr2": "9.0.5", - "magic-string": "0.30.21", + "listr2": "11.0.0", + "magic-string": "1.0.0", "mrmime": "2.0.1", - "parse5-html-rewriting-stream": "8.0.0", - "picomatch": "4.0.4", + "oxc-parser": "0.142.0", + "parse5-html-rewriting-stream": "8.0.1", + "picomatch": "4.0.5", "piscina": "5.2.0", - "rolldown": "1.0.0-rc.4", - "sass": "1.97.3", - "semver": "7.7.4", + "rolldown": "1.2.0", + "sass": "1.101.0", + "semver": "7.8.5", "source-map-support": "0.5.21", - "tinyglobby": "0.2.15", - "undici": "7.29.0", - "vite": "7.3.6", - "watchpack": "2.5.1" + "tinyglobby": "0.2.17", + "vite": "8.1.5", + "watchpack": "2.5.2" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "node": "^22.22.3 || ^24.15.0 || >=26.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "optionalDependencies": { - "lmdb": "3.5.1" + "lmdb": "3.5.6" }, "peerDependencies": { - "@angular/compiler": "^21.0.0", - "@angular/compiler-cli": "^21.0.0", - "@angular/core": "^21.0.0", - "@angular/localize": "^21.0.0", - "@angular/platform-browser": "^21.0.0", - "@angular/platform-server": "^21.0.0", - "@angular/service-worker": "^21.0.0", - "@angular/ssr": "^21.2.21", + "@angular/compiler": "^22.0.0", + "@angular/compiler-cli": "^22.0.0", + "@angular/core": "^22.0.0", + "@angular/localize": "^22.0.0", + "@angular/platform-browser": "^22.0.0", + "@angular/platform-server": "^22.0.0", + "@angular/service-worker": "^22.0.0", + "@angular/ssr": "^22.1.6", + "istanbul-lib-instrument": "^6.0.0", "karma": "^6.4.0", "less": "^4.2.0", - "ng-packagr": "^21.0.0", + "ng-packagr": "^22.0.0", "postcss": "^8.4.0", + "rollup": "^4.0.0", "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", "tslib": "^2.3.0", - "typescript": ">=5.9 <6.0", + "typescript": ">=6.0 <6.1", "vitest": "^4.0.8" }, "peerDependenciesMeta": { @@ -546,6 +374,9 @@ "@angular/ssr": { "optional": true }, + "istanbul-lib-instrument": { + "optional": true + }, "karma": { "optional": true }, @@ -558,6 +389,9 @@ "postcss": { "optional": true }, + "rollup": { + "optional": true + }, "tailwindcss": { "optional": true }, @@ -566,1265 +400,1841 @@ } } }, - "node_modules/@angular/cdk": { - "version": "21.2.14", - "resolved": "https://registry.npmmirror.com/@angular/cdk/-/cdk-21.2.14.tgz", - "integrity": "sha512-806REq/CLf37nEhmmd8Q+ILN8z/RVG2vk2n8YZ/4TdHpcBCi5ux4AxLbpMmduLwGPOzPagJ6ggRzE5fnX0rmcQ==", + "node_modules/@angular/build/node_modules/@babel/code-frame": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-8.0.0.tgz", + "integrity": "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==", + "dev": true, "license": "MIT", "dependencies": { - "parse5": "^8.0.0", - "tslib": "^2.3.0" + "@babel/helper-validator-identifier": "^8.0.0", + "js-tokens": "^10.0.0" }, - "peerDependencies": { - "@angular/common": "^21.0.0 || ^22.0.0", - "@angular/core": "^21.0.0 || ^22.0.0", - "@angular/platform-browser": "^21.0.0 || ^22.0.0", - "rxjs": "^6.5.3 || ^7.4.0" + "engines": { + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@angular/cli": { - "version": "21.2.21", - "resolved": "https://registry.npmmirror.com/@angular/cli/-/cli-21.2.21.tgz", - "integrity": "sha512-9TqWGpguYGpAsycSYX/Ji6csuPrEntdd+D2iK0TWIVQ1APCma7UmBgI1ltcATQNezxWiLTiJIMIOEuPG+cIVnw==", + "node_modules/@angular/build/node_modules/@babel/compat-data": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-8.0.0.tgz", + "integrity": "sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==", "dev": true, "license": "MIT", - "dependencies": { - "@angular-devkit/architect": "0.2102.21", - "@angular-devkit/core": "21.2.21", - "@angular-devkit/schematics": "21.2.21", - "@inquirer/prompts": "7.10.1", - "@listr2/prompt-adapter-inquirer": "3.0.5", - "@modelcontextprotocol/sdk": "1.30.0", - "@schematics/angular": "21.2.21", - "@yarnpkg/lockfile": "1.1.0", - "algoliasearch": "5.48.1", - "ini": "6.0.0", - "jsonc-parser": "3.3.1", - "listr2": "9.0.5", - "npm-package-arg": "13.0.2", - "pacote": "21.5.1", - "parse5-html-rewriting-stream": "8.0.0", - "semver": "7.7.4", - "yargs": "18.0.0", - "zod": "4.3.6" - }, - "bin": { - "ng": "bin/ng.js" - }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@angular/common": { - "version": "21.2.21", - "resolved": "https://registry.npmmirror.com/@angular/common/-/common-21.2.21.tgz", - "integrity": "sha512-ntouT80emF5osQW5XOI6PHSa6E3ZxPGIbwtIYznuiz9bBboW0TRs9YUSNg8TRfOiTBqwG8FKXCr19eyCaaTsgA==", + "node_modules/@angular/build/node_modules/@babel/core": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-8.0.1.tgz", + "integrity": "sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@babel/code-frame": "^8.0.0", + "@babel/generator": "^8.0.0", + "@babel/helper-compilation-targets": "^8.0.0", + "@babel/helpers": "^8.0.0", + "@babel/parser": "^8.0.0", + "@babel/template": "^8.0.0", + "@babel/traverse": "^8.0.0", + "@babel/types": "^8.0.0", + "@types/gensync": "^1.0.5", + "convert-source-map": "^2.0.0", + "empathic": "^2.0.1", + "gensync": "^1.0.0-beta.2", + "import-meta-resolve": "^4.2.0", + "json5": "^2.2.3", + "obug": "^2.1.1", + "semver": "^7.7.3" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.18.0 || >=24.11.0" }, - "peerDependencies": { - "@angular/core": "21.2.21", - "rxjs": "^6.5.3 || ^7.4.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@angular/compiler": { - "version": "21.2.21", - "resolved": "https://registry.npmmirror.com/@angular/compiler/-/compiler-21.2.21.tgz", - "integrity": "sha512-v2cxgmfsXVPCFivnbTmCdr1X+iDct9ZZUy7InDdKbTSwJld8zSyWwBVr3/8xzZ9hV4F41la5IxIyRyej52Mslw==", + "node_modules/@angular/build/node_modules/@babel/generator": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-8.0.0.tgz", + "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@angular/compiler-cli": { - "version": "21.2.21", - "resolved": "https://registry.npmmirror.com/@angular/compiler-cli/-/compiler-cli-21.2.21.tgz", - "integrity": "sha512-6xOrFUYYO9rDAUf+CnlqPtO1qFR4wOnKfWh9HXN3hWJ5nboo5R0K66arpw+TFYoqjhDLZcH4k7pVVnvUCJTlYg==", + "node_modules/@angular/build/node_modules/@babel/helper-compilation-targets": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.0.tgz", + "integrity": "sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "7.29.7", - "@jridgewell/sourcemap-codec": "^1.4.14", - "chokidar": "^5.0.0", - "convert-source-map": "^1.5.1", - "reflect-metadata": "^0.2.0", - "semver": "^7.0.0", - "tslib": "^2.3.0", - "yargs": "^18.0.0" - }, - "bin": { - "ng-xi18n": "bundles/src/bin/ng_xi18n.js", - "ngc": "bundles/src/bin/ngc.js" + "@babel/compat-data": "^8.0.0", + "@babel/helper-validator-option": "^8.0.0", + "browserslist": "^4.24.0", + "lru-cache": "^11.0.0", + "semver": "^7.7.3" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/compiler": "21.2.21", - "typescript": ">=5.9 <6.1" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@angular/core": { - "version": "21.2.21", - "resolved": "https://registry.npmmirror.com/@angular/core/-/core-21.2.21.tgz", - "integrity": "sha512-87moY/3TmowvBcM7zYJQcpXg8O5Ro/6uQ2OdVILl7Tq9lBZbQlZcw5N+6b8s8LwRX4ka6srFVeVLW6JoJ1cFUw==", + "node_modules/@angular/build/node_modules/@babel/helper-globals": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-8.0.0.tgz", + "integrity": "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==", + "dev": true, "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/compiler": "21.2.21", - "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.15.0 || ~0.16.0" - }, - "peerDependenciesMeta": { - "@angular/compiler": { - "optional": true - }, - "zone.js": { - "optional": true - } + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@angular/forms": { - "version": "21.2.21", - "resolved": "https://registry.npmmirror.com/@angular/forms/-/forms-21.2.21.tgz", - "integrity": "sha512-PqsY1NtvHyQ9bxnguiChXfbqFRCBJiL3wfW6wzVcYIA59QLsKKNmYitoJf3KOKN0vqCU8ley18AvJwbohJPsCw==", + "node_modules/@angular/build/node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "dev": true, "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "tslib": "^2.3.0" - }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/common": "21.2.21", - "@angular/core": "21.2.21", - "@angular/platform-browser": "21.2.21", - "rxjs": "^6.5.3 || ^7.4.0" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@angular/material": { - "version": "21.2.14", - "resolved": "https://registry.npmmirror.com/@angular/material/-/material-21.2.14.tgz", - "integrity": "sha512-fMQca8VRtei93JRRG9qQ+u08DCb0nga59Esoakq5yx3+A1NfdpFeUS1tBns56U04o8KAaIAwZK3NBqXz8ZKNqg==", + "node_modules/@angular/build/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.4", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", + "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", + "dev": true, "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/cdk": "21.2.14", - "@angular/common": "^21.0.0 || ^22.0.0", - "@angular/core": "^21.0.0 || ^22.0.0", - "@angular/forms": "^21.0.0 || ^22.0.0", - "@angular/platform-browser": "^21.0.0 || ^22.0.0", - "rxjs": "^6.5.3 || ^7.4.0" + "engines": { + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@angular/material-date-fns-adapter": { - "version": "21.2.14", - "resolved": "https://registry.npmmirror.com/@angular/material-date-fns-adapter/-/material-date-fns-adapter-21.2.14.tgz", - "integrity": "sha512-PvfX/Y+6ml8G+Zacgmp52nerI4fQmPtCKPDweUVA5Drm8Ygoo3zdVS9UnB+74KuH56I+AS5IZ8usJSwdV+z1UQ==", + "node_modules/@angular/build/node_modules/@babel/helper-validator-option": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz", + "integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==", + "dev": true, "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/core": "^21.0.0 || ^22.0.0", - "@angular/material": "21.2.14", - "date-fns": ">2.20.0 <5.0" + "engines": { + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@angular/platform-browser": { - "version": "21.2.21", - "resolved": "https://registry.npmmirror.com/@angular/platform-browser/-/platform-browser-21.2.21.tgz", - "integrity": "sha512-WtdVQzbAZEcUuRCV4kUG2z3zeePek60zW5WNCAdMgqoNWuxlt38BOOwTL9d7WohojJB55Ny4WeRrQYF7xyQgMQ==", + "node_modules/@angular/build/node_modules/@babel/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.0" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/animations": "21.2.21", - "@angular/common": "21.2.21", - "@angular/core": "21.2.21" - }, - "peerDependenciesMeta": { - "@angular/animations": { - "optional": true - } + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@angular/router": { - "version": "21.2.21", - "resolved": "https://registry.npmmirror.com/@angular/router/-/router-21.2.21.tgz", - "integrity": "sha512-Db7PfrZob1cUzwNVBSyT6Swyvw+s5uXdmUyEEYo2r0XiK5eXZFsVBeynu5I4Lltw91ktky6+SCn17aA6s+DvDA==", + "node_modules/@angular/build/node_modules/@babel/parser": { + "version": "8.0.4", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-8.0.4.tgz", + "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@babel/types": "^8.0.4" }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "bin": { + "parser": "bin/babel-parser.js" }, - "peerDependencies": { - "@angular/common": "21.2.21", - "@angular/core": "21.2.21", - "@angular/platform-browser": "21.2.21", - "rxjs": "^6.5.3 || ^7.4.0" + "engines": { + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@asamuzakjp/css-color": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "node_modules/@angular/build/node_modules/@babel/template": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-8.0.0.tgz", + "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==", "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" + "@babel/code-frame": "^8.0.0", + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/@angular/build/node_modules/@babel/traverse": { + "version": "8.0.4", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-8.0.4.tgz", + "integrity": "sha512-bZnmqzGG8UZneG1lLxBoWIH0G6Gr1D846Yu4/3XnY6FhCndMR49u26nTY08u/dAxWmLWF9vGQOuC+84FfIUoeg==", "dev": true, - "license": "ISC" - }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "@babel/code-frame": "^8.0.0", + "@babel/generator": "^8.0.0", + "@babel/helper-globals": "^8.0.0", + "@babel/parser": "^8.0.4", + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.4", + "obug": "^2.1.1" }, "engines": { - "node": ">=6.9.0" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "node_modules/@angular/build/node_modules/@babel/types": { + "version": "8.0.4", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-8.0.4.tgz", + "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" + }, "engines": { - "node": ">=6.9.0" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "node_modules/@angular/build/node_modules/agent-base": { + "version": "9.0.0", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-9.0.0.tgz", + "integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "node": ">= 20" } }, - "node_modules/@babel/core/node_modules/convert-source-map": { + "node_modules/@angular/build/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@angular/build/node_modules/https-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz", + "integrity": "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" } }, - "node_modules/@babel/generator": { - "version": "7.29.8", - "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.8.tgz", - "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "node_modules/@angular/build/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular/build/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@angular/build/node_modules/magic-string": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-1.0.0.tgz", + "integrity": "sha512-CGvjzMN08iv6w1mm4/x3Gh1hLb4VnyRUA15FFpl6CsCIGGoe36k7kY5KNz9QDbSBN5I/fWHM6ZlIkUTa5xdUEA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.8", - "@babel/types": "^7.29.8", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "node_modules/@angular/build/node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=6.9.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "dev": true, + "node_modules/@angular/cdk": { + "version": "22.1.4", + "resolved": "https://registry.npmmirror.com/@angular/cdk/-/cdk-22.1.4.tgz", + "integrity": "sha512-//KICh/rxeLbYLgNj8HawaliVAPNlSV0aRmki45Tf/YhoXVhzon9DfFqCs5u1cLvmMkMTHdFm5ghGTB8SUv3DA==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "parse5": "^8.0.0", + "tslib": "^2.3.0" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@angular/common": "^22.0.0 || ^23.0.0", + "@angular/core": "^22.0.0 || ^23.0.0", + "@angular/platform-browser": "^22.0.0 || ^23.0.0", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@angular/cli": { + "version": "22.1.6", + "resolved": "https://registry.npmmirror.com/@angular/cli/-/cli-22.1.6.tgz", + "integrity": "sha512-HT3OzYkSpCyXMTgD5G1tsS7vkrY8cYJ/JgSjRjrpwOAooSMtKF1hv7BgBcn79sKg4eaiPLrDpQLXP93vHxgSFg==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "@angular-devkit/architect": "0.2201.6", + "@angular-devkit/core": "22.1.6", + "@angular-devkit/schematics": "22.1.6", + "@inquirer/prompts": "8.5.2", + "@listr2/prompt-adapter-inquirer": "4.2.5", + "@modelcontextprotocol/sdk": "1.30.0", + "@schematics/angular": "22.1.6", + "jsonc-parser": "3.3.1", + "listr2": "11.0.0", + "npm-package-arg": "14.0.0", + "parse5-html-rewriting-stream": "8.0.1", + "semver": "7.8.5", + "yargs": "18.1.0", + "zod": "4.4.3" + }, "bin": { - "semver": "bin/semver.js" + "ng": "bin/ng.js" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "dev": true, + "node_modules/@angular/common": { + "version": "22.1.4", + "resolved": "https://registry.npmmirror.com/@angular/common/-/common-22.1.4.tgz", + "integrity": "sha512-UtWCloA/d0I5twV9Lu/Fcc7T6uQb40f/k0Hj5HLohIsiLTyvXVxWINBfa7LWWLtcq/0UOIY/yCs9Zq9Sj4dWqA==", "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, "engines": { - "node": ">=6.9.0" + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "@angular/core": "22.1.4", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "dev": true, + "node_modules/@angular/compiler": { + "version": "22.1.4", + "resolved": "https://registry.npmmirror.com/@angular/compiler/-/compiler-22.1.4.tgz", + "integrity": "sha512-o+W0shjR5KmPPWchFmqN9bLINYAy14zIW//g9T5WqKDMOqjt4IbeY6AKOoE1db/dXffprLEH9nyPOmi19iKNxg==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "node_modules/@angular/compiler-cli": { + "version": "22.1.4", + "resolved": "https://registry.npmmirror.com/@angular/compiler-cli/-/compiler-cli-22.1.4.tgz", + "integrity": "sha512-h9CcVG614TJLbk+dswlzSjYARYJh2MLOEwcnpt9zUGtOffhrrSS9BFJGjSwzdDSVM3J5mvf5ZuW+0hPAuRRoOA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@babel/core": "8.0.1", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^5.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^18.0.0" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js" }, "engines": { - "node": ">=6.9.0" + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@angular/compiler": "22.1.4", + "typescript": ">=6.0 <6.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "node_modules/@angular/compiler-cli/node_modules/@babel/code-frame": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-8.0.0.tgz", + "integrity": "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/helper-validator-identifier": "^8.0.0", + "js-tokens": "^10.0.0" }, "engines": { - "node": ">=6.9.0" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "node_modules/@angular/compiler-cli/node_modules/@babel/compat-data": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-8.0.0.tgz", + "integrity": "sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "node_modules/@angular/compiler-cli/node_modules/@babel/core": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-8.0.1.tgz", + "integrity": "sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==", + "dev": true, "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/generator": "^8.0.0", + "@babel/helper-compilation-targets": "^8.0.0", + "@babel/helpers": "^8.0.0", + "@babel/parser": "^8.0.0", + "@babel/template": "^8.0.0", + "@babel/traverse": "^8.0.0", + "@babel/types": "^8.0.0", + "@types/gensync": "^1.0.5", + "convert-source-map": "^2.0.0", + "empathic": "^2.0.1", + "gensync": "^1.0.0-beta.2", + "import-meta-resolve": "^4.2.0", + "json5": "^2.2.3", + "obug": "^2.1.1", + "semver": "^7.7.3" + }, "engines": { - "node": ">=6.9.0" + "node": "^22.18.0 || >=24.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } + "license": "MIT" }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "node_modules/@angular/compiler-cli/node_modules/@babel/generator": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-8.0.0.tgz", + "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" }, "engines": { - "node": ">=6.9.0" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@babel/parser": { - "version": "7.29.8", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.8.tgz", - "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "node_modules/@angular/compiler-cli/node_modules/@babel/helper-compilation-targets": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.0.tgz", + "integrity": "sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.8" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@babel/compat-data": "^8.0.0", + "@babel/helper-validator-option": "^8.0.0", + "browserslist": "^4.24.0", + "lru-cache": "^11.0.0", + "semver": "^7.7.3" }, "engines": { - "node": ">=6.0.0" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "node_modules/@angular/compiler-cli/node_modules/@babel/helper-globals": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-8.0.0.tgz", + "integrity": "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@babel/traverse": { - "version": "7.29.8", - "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.8.tgz", - "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "node_modules/@angular/compiler-cli/node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.8", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.8", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.8", - "debug": "^4.3.1" - }, "engines": { - "node": ">=6.9.0" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@babel/types": { - "version": "7.29.8", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "node_modules/@angular/compiler-cli/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.4", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", + "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmmirror.com/@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": "^22.18.0 || >=24.11.0" } }, - "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "node_modules/@angular/compiler-cli/node_modules/@babel/helper-validator-option": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz", + "integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==", "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": "^22.18.0 || >=24.11.0" } }, - "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "node_modules/@angular/compiler-cli/node_modules/@babel/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==", "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" + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.0" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmmirror.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "node_modules/@angular/compiler-cli/node_modules/@babel/parser": { + "version": "8.0.4", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-8.0.4.tgz", + "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "@babel/types": "^8.0.4" }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "node_modules/@angular/compiler-cli/node_modules/@babel/template": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-8.0.0.tgz", + "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0" + }, "engines": { - "node": ">=18" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@emnapi/core": { - "version": "1.11.3", - "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.3.tgz", - "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "node_modules/@angular/compiler-cli/node_modules/@babel/traverse": { + "version": "8.0.4", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-8.0.4.tgz", + "integrity": "sha512-bZnmqzGG8UZneG1lLxBoWIH0G6Gr1D846Yu4/3XnY6FhCndMR49u26nTY08u/dAxWmLWF9vGQOuC+84FfIUoeg==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.3", - "tslib": "^2.4.0" + "@babel/code-frame": "^8.0.0", + "@babel/generator": "^8.0.0", + "@babel/helper-globals": "^8.0.0", + "@babel/parser": "^8.0.4", + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.4", + "obug": "^2.1.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@emnapi/runtime": { - "version": "1.11.3", - "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.3.tgz", - "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "node_modules/@angular/compiler-cli/node_modules/@babel/types": { + "version": "8.0.4", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-8.0.4.tgz", + "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "tslib": "^2.4.0" + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", - "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", + "node_modules/@angular/compiler-cli/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } + "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@angular/compiler-cli/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "license": "BlueOak-1.0.0", "engines": { - "node": ">=18" + "node": "20 || >=22" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@angular/core": { + "version": "22.1.4", + "resolved": "https://registry.npmmirror.com/@angular/core/-/core-22.1.4.tgz", + "integrity": "sha512-0dW+lw8nHo6RGBUOXtjs02vjHIPJ8rEbw4fd0NH3Dii4p+gIB4ZaTw+o7Se3Nf4m1oZ8+2qAYmWVHXlfhKcCDg==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "tslib": "^2.3.0" + }, "engines": { - "node": ">=18" + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "@angular/compiler": "22.1.4", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0 || ~0.16.0" + }, + "peerDependenciesMeta": { + "@angular/compiler": { + "optional": true + }, + "zone.js": { + "optional": true + } } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@angular/forms": { + "version": "22.1.4", + "resolved": "https://registry.npmmirror.com/@angular/forms/-/forms-22.1.4.tgz", + "integrity": "sha512-w8ZXe4oURrb/DS5zAFrR4AmGXC84sdLeOKXRM76ndRRgSCzyRLEe4wDU1KHnDejNVIGvoSZYJJjOajG1lpj8TA==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "tslib": "^2.3.0", + "zod": "^4.0.10" + }, "engines": { - "node": ">=18" + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "@angular/common": "22.1.4", + "@angular/core": "22.1.4", + "@angular/platform-browser": "22.1.4", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@angular/material": { + "version": "22.1.4", + "resolved": "https://registry.npmmirror.com/@angular/material/-/material-22.1.4.tgz", + "integrity": "sha512-d4+uajpIYmyjar/4dypd5L+z8pbgkaCOsSxsrslmE0o0wK7mfFvXCkn0wwfucey5yUHXjswlKu1X5RzJZLOZ+w==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": "22.1.4", + "@angular/common": "^22.0.0 || ^23.0.0", + "@angular/core": "^22.0.0 || ^23.0.0", + "@angular/forms": "^22.0.0 || ^23.0.0", + "@angular/platform-browser": "^22.0.0 || ^23.0.0", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@angular/material-date-fns-adapter": { + "version": "22.1.4", + "resolved": "https://registry.npmmirror.com/@angular/material-date-fns-adapter/-/material-date-fns-adapter-22.1.4.tgz", + "integrity": "sha512-dIKptChOUUI/90DWE+aWYzpeIQES/KC0A9hBQcw+DPkmaRiTjB2nOKkT8htBUjfdUHowc7NgWh64B/3r6/72hQ==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": "^22.0.0 || ^23.0.0", + "@angular/material": "22.1.4", + "date-fns": ">2.20.0 <5.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@angular/platform-browser": { + "version": "22.1.4", + "resolved": "https://registry.npmmirror.com/@angular/platform-browser/-/platform-browser-22.1.4.tgz", + "integrity": "sha512-zhEWnRShG6GjnEvCVROD68XfnrV6+HJ9bPjwZx4LsAz7spX823Nhiml8Uqpvijsa1w7UdWLU7nK/magr9IEMOQ==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "tslib": "^2.3.0" + }, "engines": { - "node": ">=18" + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "@angular/animations": "22.1.4", + "@angular/common": "22.1.4", + "@angular/core": "22.1.4" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@angular/router": { + "version": "22.1.4", + "resolved": "https://registry.npmmirror.com/@angular/router/-/router-22.1.4.tgz", + "integrity": "sha512-vCwJLRnFBSRH5LxSF8OXKBhdmJ526hVGb/5sfeLTQW/oWCdcfXb2RvHfB077m5cdqKX9sBwFVlkJ8Z8kgx/pLQ==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "tslib": "^2.3.0" + }, "engines": { - "node": ">=18" + "node": "^22.22.3 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "@angular/common": "22.1.4", + "@angular/core": "22.1.4", + "@angular/platform-browser": "22.1.4", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "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/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ], + "peer": true, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } + "peer": true }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", + "license": "ISC", "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "peer": true, + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], + "node_modules/@babel/helper-annotate-as-pure": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz", + "integrity": "sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/types": "^8.0.0" + }, "engines": { - "node": ">=18" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], + "node_modules/@babel/helper-annotate-as-pure/node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-annotate-as-pure/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.4", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", + "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-annotate-as-pure/node_modules/@babel/types": { + "version": "8.0.4", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-8.0.4.tgz", + "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" + }, "engines": { - "node": ">=18" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "netbsd" - ], + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", + "license": "ISC", "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "peer": true, + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "openbsd" - ], + "peer": true, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "openharmony" - ], + "peer": true, + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "sunos" - ], + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.10.1", - "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", - "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, + "optional": true, + "peer": true, "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": ">=6.9.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "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.npmmirror.com/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", - "dev": true, - "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.5" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/config-array/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": "*" + "node": ">=6.0.0" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "@eslint/core": "^0.17.0" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "@types/json-schema": "^7.0.15" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.6", - "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", - "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "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.3.0", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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.npmmirror.com/@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/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "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/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@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-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/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/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@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.6", + "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "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.3.0", + "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/eslintrc/node_modules/ajv": { "version": "6.15.0", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.15.0.tgz", "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", @@ -1926,16 +2336,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@gar/promise-retry": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/@gar/promise-retry/-/promise-retry-1.0.3.tgz", - "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/@harperfast/extended-iterable": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/@harperfast/extended-iterable/-/extended-iterable-1.0.3.tgz", @@ -2024,30 +2424,56 @@ } }, "node_modules/@inquirer/ansi": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/@inquirer/ansi/-/ansi-1.0.2.tgz", - "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@inquirer/ansi/-/ansi-2.0.7.tgz", + "integrity": "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" } }, "node_modules/@inquirer/checkbox": { - "version": "4.3.2", - "resolved": "https://registry.npmmirror.com/@inquirer/checkbox/-/checkbox-4.3.2.tgz", - "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "version": "5.2.3", + "resolved": "https://registry.npmmirror.com/@inquirer/checkbox/-/checkbox-5.2.3.tgz", + "integrity": "sha512-XEYX2WA8SBkLPczL6/yXPHLPCvDoptmh9v56Cy05BSV1Smk1vWy19bTC4qJBuIffw7+6l4CcaYYzGqG60RfW1g==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^12.0.1", + "@inquirer/figures": "^2.0.8", + "@inquirer/type": "^4.1.0" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/checkbox/node_modules/@inquirer/core": { + "version": "12.0.1", + "resolved": "https://registry.npmmirror.com/@inquirer/core/-/core-12.0.1.tgz", + "integrity": "sha512-JMD5Jy/ScL5TZE18m83Nw25HjqGFLoWXwnEkW7IdwwAhZpB9Bus55/WU7zn3UqR1MOCjjTQOIYpiD4vjWA3LPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.8", + "@inquirer/type": "^4.1.0", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2059,17 +2485,17 @@ } }, "node_modules/@inquirer/confirm": { - "version": "5.1.21", - "resolved": "https://registry.npmmirror.com/@inquirer/confirm/-/confirm-5.1.21.tgz", - "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "version": "6.1.1", + "resolved": "https://registry.npmmirror.com/@inquirer/confirm/-/confirm-6.1.1.tgz", + "integrity": "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2081,23 +2507,22 @@ } }, "node_modules/@inquirer/core": { - "version": "10.3.2", - "resolved": "https://registry.npmmirror.com/@inquirer/core/-/core-10.3.2.tgz", - "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "version": "11.2.1", + "resolved": "https://registry.npmmirror.com/@inquirer/core/-/core-11.2.1.tgz", + "integrity": "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7", "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.3" + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2109,18 +2534,45 @@ } }, "node_modules/@inquirer/editor": { - "version": "4.2.23", - "resolved": "https://registry.npmmirror.com/@inquirer/editor/-/editor-4.2.23.tgz", - "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/@inquirer/editor/-/editor-5.3.1.tgz", + "integrity": "sha512-y43COoyVUjPWIobn2Qep/uI1drPS78aaZZZ9kVi94Tyu/GuW2N8d8Q4rifJXGAXCEAXCPTTMjD8gC1HyvM5ukA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/external-editor": "^1.0.3", - "@inquirer/type": "^3.0.10" + "@inquirer/core": "^12.0.1", + "@inquirer/external-editor": "^3.0.4", + "@inquirer/type": "^4.1.0" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor/node_modules/@inquirer/core": { + "version": "12.0.1", + "resolved": "https://registry.npmmirror.com/@inquirer/core/-/core-12.0.1.tgz", + "integrity": "sha512-JMD5Jy/ScL5TZE18m83Nw25HjqGFLoWXwnEkW7IdwwAhZpB9Bus55/WU7zn3UqR1MOCjjTQOIYpiD4vjWA3LPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.8", + "@inquirer/type": "^4.1.0", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2132,18 +2584,44 @@ } }, "node_modules/@inquirer/expand": { - "version": "4.0.23", - "resolved": "https://registry.npmmirror.com/@inquirer/expand/-/expand-4.0.23.tgz", - "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "version": "5.1.3", + "resolved": "https://registry.npmmirror.com/@inquirer/expand/-/expand-5.1.3.tgz", + "integrity": "sha512-3NQJiXNJ/aj9wiAsr7pECdp5Qe9J0X9YUJCKsaFXS+ddOxfL6J4AIl3w3T4Gq3kK0WsQY5GMoDokK5X94m6lHw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" + "@inquirer/core": "^12.0.1", + "@inquirer/type": "^4.1.0" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand/node_modules/@inquirer/core": { + "version": "12.0.1", + "resolved": "https://registry.npmmirror.com/@inquirer/core/-/core-12.0.1.tgz", + "integrity": "sha512-JMD5Jy/ScL5TZE18m83Nw25HjqGFLoWXwnEkW7IdwwAhZpB9Bus55/WU7zn3UqR1MOCjjTQOIYpiD4vjWA3LPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.8", + "@inquirer/type": "^4.1.0", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2155,17 +2633,17 @@ } }, "node_modules/@inquirer/external-editor": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/@inquirer/external-editor/-/external-editor-1.0.3.tgz", - "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@inquirer/external-editor/-/external-editor-3.0.4.tgz", + "integrity": "sha512-tZbbaK2ovq6vlrRBNQvjrypmrED/p5x2ncIHQ79cD55tei3dD96v5glMMA+6tiq7K104i/25DVYKWVPJuV6ptA==", "dev": true, "license": "MIT", "dependencies": { "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" + "iconv-lite": "^0.7.2" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2177,27 +2655,54 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.15", - "resolved": "https://registry.npmmirror.com/@inquirer/figures/-/figures-1.0.15.tgz", - "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "version": "2.0.8", + "resolved": "https://registry.npmmirror.com/@inquirer/figures/-/figures-2.0.8.tgz", + "integrity": "sha512-tApbon79GM9ry56ja/Ud3SY2CL4TQsao9fIwDQbgTeNY55025GdMzQ2+UdegV/lx51VNGUB59M0v0nMpybYY4Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" } }, "node_modules/@inquirer/input": { - "version": "4.3.1", - "resolved": "https://registry.npmmirror.com/@inquirer/input/-/input-4.3.1.tgz", - "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "version": "5.1.4", + "resolved": "https://registry.npmmirror.com/@inquirer/input/-/input-5.1.4.tgz", + "integrity": "sha512-3xQkQrOvgOzpSN2ciTVdRDlg1FWMCA8l+0KfB6SNlILoTCGzJTzO/gc0Rwjcb3usuGyKdaGtI6OiyMdeMeLWkg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" + "@inquirer/core": "^12.0.1", + "@inquirer/type": "^4.1.0" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/input/node_modules/@inquirer/core": { + "version": "12.0.1", + "resolved": "https://registry.npmmirror.com/@inquirer/core/-/core-12.0.1.tgz", + "integrity": "sha512-JMD5Jy/ScL5TZE18m83Nw25HjqGFLoWXwnEkW7IdwwAhZpB9Bus55/WU7zn3UqR1MOCjjTQOIYpiD4vjWA3LPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.8", + "@inquirer/type": "^4.1.0", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2209,17 +2714,44 @@ } }, "node_modules/@inquirer/number": { - "version": "3.0.23", - "resolved": "https://registry.npmmirror.com/@inquirer/number/-/number-3.0.23.tgz", - "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/@inquirer/number/-/number-4.2.1.tgz", + "integrity": "sha512-5KaqwZNLRpUuWcoCrYghPP9TMaXL5v2Sk4xqePM7RCVegcJStoXdWibio60YIC1bec+z1fCyb67N6XPJIkZtGA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" + "@inquirer/core": "^12.0.1", + "@inquirer/type": "^4.1.0" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number/node_modules/@inquirer/core": { + "version": "12.0.1", + "resolved": "https://registry.npmmirror.com/@inquirer/core/-/core-12.0.1.tgz", + "integrity": "sha512-JMD5Jy/ScL5TZE18m83Nw25HjqGFLoWXwnEkW7IdwwAhZpB9Bus55/WU7zn3UqR1MOCjjTQOIYpiD4vjWA3LPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.8", + "@inquirer/type": "^4.1.0", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2231,18 +2763,45 @@ } }, "node_modules/@inquirer/password": { - "version": "4.0.23", - "resolved": "https://registry.npmmirror.com/@inquirer/password/-/password-4.0.23.tgz", - "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/@inquirer/password/-/password-5.2.0.tgz", + "integrity": "sha512-CvVcW09emkBESEOW+4R8CjLNkP3fB3XrjeL8CDvfpjgrJN+V9oerXmJAXXM3l+4xqYPD5Yaujzy/Ph0PLOdDuA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^12.0.1", + "@inquirer/type": "^4.1.0" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password/node_modules/@inquirer/core": { + "version": "12.0.1", + "resolved": "https://registry.npmmirror.com/@inquirer/core/-/core-12.0.1.tgz", + "integrity": "sha512-JMD5Jy/ScL5TZE18m83Nw25HjqGFLoWXwnEkW7IdwwAhZpB9Bus55/WU7zn3UqR1MOCjjTQOIYpiD4vjWA3LPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.8", + "@inquirer/type": "^4.1.0", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2254,25 +2813,25 @@ } }, "node_modules/@inquirer/prompts": { - "version": "7.10.1", - "resolved": "https://registry.npmmirror.com/@inquirer/prompts/-/prompts-7.10.1.tgz", - "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "version": "8.5.2", + "resolved": "https://registry.npmmirror.com/@inquirer/prompts/-/prompts-8.5.2.tgz", + "integrity": "sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^4.3.2", - "@inquirer/confirm": "^5.1.21", - "@inquirer/editor": "^4.2.23", - "@inquirer/expand": "^4.0.23", - "@inquirer/input": "^4.3.1", - "@inquirer/number": "^3.0.23", - "@inquirer/password": "^4.0.23", - "@inquirer/rawlist": "^4.1.11", - "@inquirer/search": "^3.2.2", - "@inquirer/select": "^4.4.2" + "@inquirer/checkbox": "^5.2.1", + "@inquirer/confirm": "^6.1.1", + "@inquirer/editor": "^5.2.2", + "@inquirer/expand": "^5.1.1", + "@inquirer/input": "^5.1.2", + "@inquirer/number": "^4.1.1", + "@inquirer/password": "^5.1.1", + "@inquirer/rawlist": "^5.3.1", + "@inquirer/search": "^4.2.1", + "@inquirer/select": "^5.2.1" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2284,18 +2843,44 @@ } }, "node_modules/@inquirer/rawlist": { - "version": "4.1.11", - "resolved": "https://registry.npmmirror.com/@inquirer/rawlist/-/rawlist-4.1.11.tgz", - "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "version": "5.3.3", + "resolved": "https://registry.npmmirror.com/@inquirer/rawlist/-/rawlist-5.3.3.tgz", + "integrity": "sha512-Mu7WrtmDLaXBDEyrRLS70SZgX9ZSm4Up1w0ZxiH8C1OOp9oaVCn2k8q3QGgmlnhsKYUhuaU3zFWhAP6wxkVIMA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" + "@inquirer/core": "^12.0.1", + "@inquirer/type": "^4.1.0" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist/node_modules/@inquirer/core": { + "version": "12.0.1", + "resolved": "https://registry.npmmirror.com/@inquirer/core/-/core-12.0.1.tgz", + "integrity": "sha512-JMD5Jy/ScL5TZE18m83Nw25HjqGFLoWXwnEkW7IdwwAhZpB9Bus55/WU7zn3UqR1MOCjjTQOIYpiD4vjWA3LPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.8", + "@inquirer/type": "^4.1.0", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2307,19 +2892,18 @@ } }, "node_modules/@inquirer/search": { - "version": "3.2.2", - "resolved": "https://registry.npmmirror.com/@inquirer/search/-/search-3.2.2.tgz", - "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/@inquirer/search/-/search-4.3.1.tgz", + "integrity": "sha512-0VWOvsHWI0rPj6CG70MoP4oXNCB6adcyN8bVFZXnh11eLDdPIK2f2XCmva78acPPDfJisfab7qNakwBh7hdBXw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" + "@inquirer/core": "^12.0.1", + "@inquirer/figures": "^2.0.8", + "@inquirer/type": "^4.1.0" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2330,21 +2914,23 @@ } } }, - "node_modules/@inquirer/select": { - "version": "4.4.2", - "resolved": "https://registry.npmmirror.com/@inquirer/select/-/select-4.4.2.tgz", - "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "node_modules/@inquirer/search/node_modules/@inquirer/core": { + "version": "12.0.1", + "resolved": "https://registry.npmmirror.com/@inquirer/core/-/core-12.0.1.tgz", + "integrity": "sha512-JMD5Jy/ScL5TZE18m83Nw25HjqGFLoWXwnEkW7IdwwAhZpB9Bus55/WU7zn3UqR1MOCjjTQOIYpiD4vjWA3LPw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.8", + "@inquirer/type": "^4.1.0", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2355,14 +2941,20 @@ } } }, - "node_modules/@inquirer/type": { - "version": "3.0.10", - "resolved": "https://registry.npmmirror.com/@inquirer/type/-/type-3.0.10.tgz", - "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "node_modules/@inquirer/select": { + "version": "5.2.3", + "resolved": "https://registry.npmmirror.com/@inquirer/select/-/select-5.2.3.tgz", + "integrity": "sha512-KuRTodDa6xBXX2noIpjuitpX/QT7Sfav7dIZ/OfUY54Hxg95nrGoshSzxx6Ey7qqLbImKdiGkSDt7KjPXjQgmA==", "dev": true, "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^12.0.1", + "@inquirer/figures": "^2.0.8", + "@inquirer/type": "^4.1.0" + }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2373,17 +2965,49 @@ } } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "node_modules/@inquirer/select/node_modules/@inquirer/core": { + "version": "12.0.1", + "resolved": "https://registry.npmmirror.com/@inquirer/core/-/core-12.0.1.tgz", + "integrity": "sha512-JMD5Jy/ScL5TZE18m83Nw25HjqGFLoWXwnEkW7IdwwAhZpB9Bus55/WU7zn3UqR1MOCjjTQOIYpiD4vjWA3LPw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^7.0.4" + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.8", + "@inquirer/type": "^4.1.0", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/@inquirer/type/-/type-4.1.0.tgz", + "integrity": "sha512-FMiJpuHUG3Dk0ex+UIXkre7i+i4OcwHWk9YdcVtZHFwb/r2rnrU2ipTCNAB7A+QOP0ryzIcqOfy76fRyyvOEAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@istanbuljs/schema": { @@ -2392,6 +3016,8 @@ "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -2412,6 +3038,8 @@ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -2477,26 +3105,26 @@ "license": "MIT" }, "node_modules/@listr2/prompt-adapter-inquirer": { - "version": "3.0.5", - "resolved": "https://registry.npmmirror.com/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.5.tgz", - "integrity": "sha512-WELs+hj6xcilkloBXYf9XXK8tYEnKsgLj01Xl5ONUJpKjmT5hGVUzNUS5tooUxs7pGMrw+jFD/41WpqW4V3LDA==", + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-4.2.5.tgz", + "integrity": "sha512-pYGy9dTdTwXdasPgyohkr0HoQ4FrkAzFnsUZl/gcnadDArbpZ8e+fgr+F9WBdNEl2y00mb9bCM4WgmoBkZJ27A==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/type": "^3.0.8" + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.13.0" }, "peerDependencies": { - "@inquirer/prompts": ">= 3 < 8", - "listr2": "9.0.5" + "@inquirer/prompts": ">= 3 < 9", + "listr2": "11.0.0" } }, "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "3.5.1", - "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.5.1.tgz", - "integrity": "sha512-tpfN4kKrrMpQ+If1l8bhmoNkECJi0iOu6AEdrTJvWVC+32sLxTARX5Rsu579mPImRP9YFWfWgeRQ5oav7zApQQ==", + "version": "3.5.6", + "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.5.6.tgz", + "integrity": "sha512-mY5FG4TjPAkY4P0w+OhHaUka5mDh2TX2WKYIwuKzJ1zeW3VvRgxdam/lGJTquI+bthTx5CSHDW+BAQCnNAzkEA==", "cpu": [ "arm64" ], @@ -2508,9 +3136,9 @@ ] }, "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "3.5.1", - "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.5.1.tgz", - "integrity": "sha512-+a2tTfc3rmWhLAolFUWRgJtpSuu+Fw/yjn4rF406NMxhfjbMuiOUTDRvRlMFV+DzyjkwnokisskHbCWkS3Ly5w==", + "version": "3.5.6", + "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.5.6.tgz", + "integrity": "sha512-foa+pwitysO8k+xhs7psBFfTKnVgR69NlZRRTHaFVDqphh7AdGpLeyRzKw/ofatr/sN6TiHRRW6mmop0ZrrppQ==", "cpu": [ "x64" ], @@ -2522,9 +3150,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm": { - "version": "3.5.1", - "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.5.1.tgz", - "integrity": "sha512-0EgcE6reYr8InjD7V37EgXcYrloqpxVPINy3ig1MwDSbl6LF/vXTYRH9OE1Ti1D8YZnB35ZH9aTcdfSb5lql2A==", + "version": "3.5.6", + "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.5.6.tgz", + "integrity": "sha512-QR4YRyR5h5Z8eGXrNQjiyo2NNDfqi3tCc9dQG5Is1blCt+qWw1ZoBWhlWAr5d+jshkifMIJjVHzHGKbkKzF8Tw==", "cpu": [ "arm" ], @@ -2536,9 +3164,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "3.5.1", - "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.5.1.tgz", - "integrity": "sha512-aoERa5B6ywXdyFeYGQ1gbQpkMkDbEo45qVoXE5QpIRavqjnyPwjOulMkmkypkmsbJ5z4Wi0TBztON8agCTG0Vg==", + "version": "3.5.6", + "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.5.6.tgz", + "integrity": "sha512-HmiyFFdJa38s1heCMSooSPaBSFTHJ3C+ERPp28xAPlDX1YiALJVOgbry065nXd8Y7KISWjnw05zpG1RX8IfftA==", "cpu": [ "arm64" ], @@ -2550,9 +3178,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-x64": { - "version": "3.5.1", - "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.5.1.tgz", - "integrity": "sha512-SqNDY1+vpji7bh0sFH5wlWyFTOzjbDOl0/kB5RLLYDAFyd/uw3n7wyrmas3rYPpAW7z18lMOi1yKlTPv967E3g==", + "version": "3.5.6", + "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.5.6.tgz", + "integrity": "sha512-ADzCuCF2cTNiX9kDScqcz1fjnAkxPpQNneV3KFTdV3wWtVlI2sTGzySoMTgDpinkMMFj1NTJlxA6XR8fwc4hlA==", "cpu": [ "x64" ], @@ -2564,9 +3192,9 @@ ] }, "node_modules/@lmdb/lmdb-win32-arm64": { - "version": "3.5.1", - "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.5.1.tgz", - "integrity": "sha512-50v0O1Lt37cwrmR9vWZK5hRW0Aw+KEmxJJ75fge/zIYdvNKB/0bSMSVR5Uc2OV9JhosIUyklOmrEvavwNJ8D6w==", + "version": "3.5.6", + "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.5.6.tgz", + "integrity": "sha512-J7A9aEQsQiv0TYtBGL7NDIPp2lOS8nnl+zm4sWZm1xlsTTaQ4PgD096Adzdrk27rw3UxCkDXdCUa4ax41oztBQ==", "cpu": [ "arm64" ], @@ -2578,9 +3206,9 @@ ] }, "node_modules/@lmdb/lmdb-win32-x64": { - "version": "3.5.1", - "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.5.1.tgz", - "integrity": "sha512-qwosvPyl+zpUlp3gRb7UcJ3H8S28XHCzkv0Y0EgQToXjQP91ZD67EHSCDmaLjtKhe+GVIW5om1KUpzVLA0l6pg==", + "version": "3.5.6", + "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.5.6.tgz", + "integrity": "sha512-1g7G0knRX2iV/voDu54yxrGqw5Dk0w2oIYb7dgJq8IkOi+m7wbD8Q3QpPFjh0C01G58S88dqGn03len6UPCXsg==", "cpu": [ "x64" ], @@ -2729,6 +3357,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^22.20 || ^24.12 || >=25" } @@ -3110,221 +3739,355 @@ "fastq": "^1.6.0" }, "engines": { - "node": ">= 8" + "node": ">= 8" + } + }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.142.0.tgz", + "integrity": "sha512-ZiRGDutGsv1G6bL/ozy/koC0Sv39T1DqyoC4KD1DOy9ZoACm1O5UWhEK2c02Qdk+4lfLVkvFa/mQ0fm/4h1BtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.142.0.tgz", + "integrity": "sha512-WZkvGRLNQTz8lR9zP5nLjUdlroRCopBu3g9zF1p/laE6DzT1UbQo8Rdz5MWhaJUPYg/6gp+jo7HUgsyKaN1FtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.142.0.tgz", + "integrity": "sha512-l4khS8LQOOVYsGRVARo1gSaCT/aBSceUVXgtovWc2+drnxVuDr082WA3OCHVdVzIz5JIrP/y9CWsSKxBDNmYGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.142.0.tgz", + "integrity": "sha512-QBsNF3nqlXmcH2B1YOPqQYmCJoy4HuIjUxGbBO/k5JAJUl68ghU2psRY2zPk+RyBaWqKP/qfL4oaFgEMCdwskA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/agent": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/@npmcli/agent/-/agent-4.0.2.tgz", - "integrity": "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg==", + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.142.0.tgz", + "integrity": "sha512-b7Q7m4Cqc6XqNhri3R+QhU+GVy646Pn+bkdhrDdWym/Fdi0ZUa+d73H9dm5H91JtbtAQ/z1d8XKMW3oOV8a4tQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^11.2.1", - "socks-proxy-agent": "^8.0.3" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.142.0.tgz", + "integrity": "sha512-3riVS5IhdH3uCZj1Y9ftDQlR0dvLsIlw/edrRqk8JhgNd5K0XSs+UBtgh50N13CAlW9/TXj6sVGXaKNBocd0Yg==", + "cpu": [ + "arm" + ], "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "20 || >=22" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/fs": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/@npmcli/fs/-/fs-5.0.0.tgz", - "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.142.0.tgz", + "integrity": "sha512-NmXUOpgpTSkhl795TiXmWppTwmSJ92RC1qvD6e4XOF+slgmo3e6Ah+kEu+6AN8s7NAOEwqGmir58MgSQSWmBSA==", + "cpu": [ + "arm" + ], "dev": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/git": { - "version": "7.0.2", - "resolved": "https://registry.npmmirror.com/@npmcli/git/-/git-7.0.2.tgz", - "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.142.0.tgz", + "integrity": "sha512-gc0EXsKtXgerujmU2Bql3u1L1HsSQ2774R83idq/FoNMPVV/RY/1ErFsvnit7KoiP/sLvzQixeUo4Ut0ic0wmw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "@gar/promise-retry": "^1.0.0", - "@npmcli/promise-spawn": "^9.0.0", - "ini": "^6.0.0", - "lru-cache": "^11.2.1", - "npm-pick-manifest": "^11.0.1", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "which": "^6.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/git/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.142.0.tgz", + "integrity": "sha512-F2XvmWSE0uWpie+jHKKIFgdVOe9ypGhkEZxKx5DuW215K6cbAC274yYaPkcM7EqY4Df3Weyhpcz3lsURyH2LVg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.142.0.tgz", + "integrity": "sha512-wLMbT21U/QxknQsk+VvNF0b9D2/aGWhcaQQQ+VYlE8FwD5+GoWZIPPXNzyHmkYyhm0KB3itL+TBavjMatqNnYA==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "20 || >=22" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/git/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.142.0.tgz", + "integrity": "sha512-+G8F/4ckwT7FCJV4H2bt09xEzJbjNCfuL4Sp1AYNaFtFMVtgIGMuJlteT82U+K0UIZ/DzAR/LDlMFnEuajG7Kw==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/installed-package-contents": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", - "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.142.0.tgz", + "integrity": "sha512-hTsHtTLxMAfCo+rpF5K3qZJKW2NpPN/CHd4mYB3y7XlSdspHkd2gehDIofP64AacA9nWQw2tY3O7wR6UY8IVOA==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "npm-bundled": "^5.0.0", - "npm-normalize-package-bin": "^5.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/node-gyp": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", - "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.142.0.tgz", + "integrity": "sha512-6y7qYY3TCUDYjqswImdTGl92y+KA/80twALegQPN27kfY+bG7Ib1+L3jbmrCZQx6wrVnai9IPsEZp07I0hx7JQ==", + "cpu": [ + "s390x" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/package-json": { - "version": "7.0.5", - "resolved": "https://registry.npmmirror.com/@npmcli/package-json/-/package-json-7.0.5.tgz", - "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.142.0.tgz", + "integrity": "sha512-i69kAWU+2LgoH5bR+zWiiu+UzAw7Oxkwv7COeJTeY19pn4e70nKQcr9Pm6cL2Z0Z54d+gl9qADlK/0yyuCPiBA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^7.0.0", - "glob": "^13.0.0", - "hosted-git-info": "^9.0.0", - "json-parse-even-better-errors": "^5.0.0", - "proc-log": "^6.0.0", - "semver": "^7.5.3", - "spdx-expression-parse": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/promise-spawn": { - "version": "9.0.1", - "resolved": "https://registry.npmmirror.com/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", - "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.142.0.tgz", + "integrity": "sha512-4SQs678MmjYVrmhAgCWD4o0vpaFszXw9xLX5p2Z9MMFcltxiLkA88wQjh80YHjPrXtpyZ2CWI5m+1yNKM0m2Pw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "which": "^6.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.142.0.tgz", + "integrity": "sha512-YHpx9N7Ln3a++Tc8rv+H7mrK1zyJQOAwCFg8LZ3lTs1T5afGWeZrLPhPT9HLnIwSjCyJqPWVMIrMxbjcmBr2oQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.142.0.tgz", + "integrity": "sha512-3pLDyY3+oogW73RM5uehNgAiR/Xfb7fvO2Q1Z1gIqZ2+50XDVQmBVlRkHXZTU4gKnQHpwETNsYQVsJ3joVB2iA==", + "cpu": [ + "wasm32" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/redact": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/@npmcli/redact/-/redact-4.0.0.tgz", - "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.142.0.tgz", + "integrity": "sha512-Had/VeVY28Oyb0K+Q4FV8KCzoBycIh93oDK6pCbya9lkzdq+ikMHMgBubsdqqlybjJmQRawCQRrnBRHyQwYvcQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/run-script": { - "version": "10.0.4", - "resolved": "https://registry.npmmirror.com/@npmcli/run-script/-/run-script-10.0.4.tgz", - "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.142.0.tgz", + "integrity": "sha512-GGi3+YphVHavvgs6gum2UXoNCqzHAmPt/nXkn8ZQZstV2Q1qZD1Mn8fz/nWrDkefHQtrG/+1/XrbMxsBTo6Svw==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^5.0.0", - "@npmcli/package-json": "^7.0.0", - "@npmcli/promise-spawn": "^9.0.0", - "node-gyp": "^12.1.0", - "proc-log": "^6.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.142.0.tgz", + "integrity": "sha512-Ny/Wv4Us1LGC/ljwNTp+Hx3r/pH15EFfeDF0p+n898gt+TtRd6C9SccHcuUhDiNTb8s5tt7jdeAMDRQZ4Vq6hg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-project/types": { - "version": "0.113.0", - "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.113.0.tgz", - "integrity": "sha512-Tp3XmgxwNQ9pEN9vxgJBAqdRamHibi76iowQ38O2I4PMpcvNRQNVsU2n1x1nv9yh0XoTrGFzf7cZSGxmixxrhA==", + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.142.0.tgz", + "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", "dev": true, "license": "MIT", "funding": { @@ -3641,9 +4404,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.4.tgz", - "integrity": "sha512-vRq9f4NzvbdZavhQbjkJBx7rRebDKYR9zHfO/Wg486+I7bSecdUapzCm5cyXoK+LHokTxgSq7A5baAXUZkIz0w==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.0.tgz", + "integrity": "sha512-9yB1l95IrJuNGDFdOYe79vdApdz6WWBCObE+rQ2LUliYUlcyFwSYIb2xb5/Ifw7dAtMy2ZqNyd8QTSOc7duAKw==", "cpu": [ "arm64" ], @@ -3658,9 +4421,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.4.tgz", - "integrity": "sha512-kFgEvkWLqt3YCgKB5re9RlIrx9bRsvyVUnaTakEpOPuLGzLpLapYxE9BufJNvPg8GjT6mB1alN4yN1NjzoeM8Q==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.0.tgz", + "integrity": "sha512-pexNaW9ACLUOaBITOpU6qVu4VrsOFIjTv6bzgu0YUATo4eUJx0V605PxwZfndpPOn0ilqGqvGQ0M8UW0IE24jg==", "cpu": [ "arm64" ], @@ -3675,9 +4438,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.4.tgz", - "integrity": "sha512-JXmaOJGsL/+rsmMfutcDjxWM2fTaVgCHGoXS7nE8Z3c9NAYjGqHvXrAhMUZvMpHS/k7Mg+X7n/MVKb7NYWKKww==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.0.tgz", + "integrity": "sha512-NqKYaq0355ZmNMG4QGpxtEDxsc7tGDhjhCm4PpE0cwnBW+5Il95LJyq414niEiaKLVjnVHBEjSo1wngKxJNiFw==", "cpu": [ "x64" ], @@ -3692,9 +4455,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.4.tgz", - "integrity": "sha512-ep3Catd6sPnHTM0P4hNEvIv5arnDvk01PfyJIJ+J3wVCG1eEaPo09tvFqdtcaTrkwQy0VWR24uz+cb4IsK53Qw==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.0.tgz", + "integrity": "sha512-3vPoHzh6eBTz9IbB0/qZdSr0Qeks2echn+I4cHu2joV74VriPDdldswksEDzrl1mBB+oPRi+67+3Ib59paxIPQ==", "cpu": [ "x64" ], @@ -3709,9 +4472,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.4.tgz", - "integrity": "sha512-LwA5ayKIpnsgXJEwWc3h8wPiS33NMIHd9BhsV92T8VetVAbGe2qXlJwNVDGHN5cOQ22R9uYvbrQir2AB+ntT2w==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.0.tgz", + "integrity": "sha512-E6NNefZ1bUVmKJq2tJkf45J4Zyczj7qm9rUT7NY+Xo2474Y13qWAwc2tvBt0BAVbmtXR1llkxXg0Ou1jbDf2SQ==", "cpu": [ "arm" ], @@ -3726,9 +4489,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.4.tgz", - "integrity": "sha512-AC1WsGdlV1MtGay/OQ4J9T7GRadVnpYRzTcygV1hKnypbYN20Yh4t6O1Sa2qRBMqv1etulUknqXjc3CTIsBu6A==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.0.tgz", + "integrity": "sha512-D+TgkdgM1vu+7/Fpf8+v0ARW+RXEP9Ccazgm8zQ4JFFd9Q7SrYQ2TakU5S5ihazQDgpKyAgZDOcIFsvoHmTZ8w==", "cpu": [ "arm64" ], @@ -3743,9 +4506,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.4.tgz", - "integrity": "sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.0.tgz", + "integrity": "sha512-wUqdwJBbAv0APN87GecstdMUtLjjNTs0hBALpxETD73mccFxdmt/XeizXDtN5RAlBwNKmI+Tg+blect2G+8IeQ==", "cpu": [ "arm64" ], @@ -3759,10 +4522,44 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.0.tgz", + "integrity": "sha512-9DtF35qR9/NrfhM4oxLplCzVVjE+KKm8Pjemi0i/sdhAWkUasjmSo8WTTubNJClhSHCfyk2yeyoXDQEDPtDAAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.0.tgz", + "integrity": "sha512-RzuHrBh8X8Hntd2N4VR02QGEciq/9JhcZoTpR/Cee6otRrlILGCf3cg2ygHuih+ZebUnWmMrDX6ITI85btO6rQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.4.tgz", - "integrity": "sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.0.tgz", + "integrity": "sha512-MK7L0018jjh1jR3mh21G2j1zAVcpscJBlPo2z19pRjv2XOYGRhaV4LyiD8HO6nCDdZln9IFgCMIV5yt4E3klGQ==", "cpu": [ "x64" ], @@ -3777,9 +4574,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.4.tgz", - "integrity": "sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.0.tgz", + "integrity": "sha512-gyrxLQ9NfGb/9LoVnC4kb9miUghw1mghnkfYvNHSnVIXriabnfgGPUP4RLcJm87q3KgYz4FYUG8IDiWUT+CpSw==", "cpu": [ "x64" ], @@ -3794,9 +4591,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.4.tgz", - "integrity": "sha512-6lcI79+X8klGiGd8yHuTgQRjuuJYNggmEml+RsyN596P23l/zf9FVmJ7K0KVKkFAeYEdg0iMUKyIxiV5vebDNQ==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.0.tgz", + "integrity": "sha512-/6VFMQGRmrhP77KXDC+StIxGzcNp5JOIyYtw0CQ8gPlzhpiIRucYfoM5FaFamHd5BJYIdH86yfP46l1p3WdrFA==", "cpu": [ "arm64" ], @@ -3811,9 +4608,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.4.tgz", - "integrity": "sha512-wz7ohsKCAIWy91blZ/1FlpPdqrsm1xpcEOQVveWoL6+aSPKL4VUcoYmmzuLTssyZxRpEwzuIxL/GDsvpjaBtOw==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.0.tgz", + "integrity": "sha512-rwdbUL465kisF24WEJLvP3JrEG6E5GRuIHt5wpMwHGERtHe4Wm2CIvtf5gTBgr2tGOHKh5NdKEAFS2VkOPE91g==", "cpu": [ "wasm32" ], @@ -3821,16 +4618,18 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.4.tgz", - "integrity": "sha512-cfiMrfuWCIgsFmcVG0IPuO6qTRHvF7NuG3wngX1RZzc6dU8FuBFb+J3MIR5WrdTNozlumfgL4cvz+R4ozBCvsQ==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.0.tgz", + "integrity": "sha512-+5suHwRiKGmhwyUaNT8a5QbrBvLFh2DbO910TEmGRH1aSxwrCezodvGQnulv4uiWEIv1Kq4ypRsJ5+O+ry1DiA==", "cpu": [ "arm64" ], @@ -3845,9 +4644,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.4.tgz", - "integrity": "sha512-p6UeR9y7ht82AH57qwGuFYn69S6CZ7LLKdCKy/8T3zS9VTrJei2/CGsTUV45Da4Z9Rbhc7G4gyWQ/Ioamqn09g==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.0.tgz", + "integrity": "sha512-WfFv6/qGufotqBSBzBYwgpCkJBk8Nj7697LL9vTz/XWc67e0r3oewu8iMRwQj3AUL45GVD7wVsPjCsAAtW66Wg==", "cpu": [ "x64" ], @@ -3862,9 +4661,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.4.tgz", - "integrity": "sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ==", + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, @@ -3880,7 +4679,8 @@ "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-android-arm64": { "version": "4.62.5", @@ -3894,7 +4694,8 @@ "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.62.5", @@ -3908,7 +4709,8 @@ "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-x64": { "version": "4.62.5", @@ -3922,7 +4724,8 @@ "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-arm64": { "version": "4.62.5", @@ -3936,7 +4739,8 @@ "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-x64": { "version": "4.62.5", @@ -3950,7 +4754,8 @@ "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { "version": "4.62.5", @@ -3964,7 +4769,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { "version": "4.62.5", @@ -3978,7 +4784,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-gnu": { "version": "4.62.5", @@ -3992,7 +4799,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.62.5", @@ -4006,7 +4814,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-loong64-gnu": { "version": "4.62.5", @@ -4020,7 +4829,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-loong64-musl": { "version": "4.62.5", @@ -4034,7 +4844,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { "version": "4.62.5", @@ -4048,7 +4859,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-ppc64-musl": { "version": "4.62.5", @@ -4062,7 +4874,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { "version": "4.62.5", @@ -4076,7 +4889,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-musl": { "version": "4.62.5", @@ -4090,7 +4904,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-s390x-gnu": { "version": "4.62.5", @@ -4104,7 +4919,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.62.5", @@ -4118,7 +4934,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.62.5", @@ -4132,7 +4949,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-openbsd-x64": { "version": "4.62.5", @@ -4146,7 +4964,8 @@ "optional": true, "os": [ "openbsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-openharmony-arm64": { "version": "4.62.5", @@ -4160,7 +4979,8 @@ "optional": true, "os": [ "openharmony" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-arm64-msvc": { "version": "4.62.5", @@ -4174,7 +4994,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-ia32-msvc": { "version": "4.62.5", @@ -4188,7 +5009,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-x64-gnu": { "version": "4.62.5", @@ -4202,7 +5024,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.62.5", @@ -4216,7 +5039,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rtsao/scc": { "version": "1.1.0", @@ -4226,132 +5050,29 @@ "license": "MIT" }, "node_modules/@schematics/angular": { - "version": "21.2.21", - "resolved": "https://registry.npmmirror.com/@schematics/angular/-/angular-21.2.21.tgz", - "integrity": "sha512-ZiR5CcDMBI+0TkP4WeazhJmu1SdIq81VvO9CbXEHBO9KQWTtuE0EQCnzQkpIo4Dyh6jDRpA6sA3gkh79vW4aNQ==", + "version": "22.1.6", + "resolved": "https://registry.npmmirror.com/@schematics/angular/-/angular-22.1.6.tgz", + "integrity": "sha512-RiD4OZJ4yuaM1aXb3pt1gjDSWwkP0jrgiCuoaBls1eabcrDmlsNuQv0ekxbcGgCRPdSJvmHRZIHHmFPzei8w3Q==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "21.2.21", - "@angular-devkit/schematics": "21.2.21", - "jsonc-parser": "3.3.1" + "@angular-devkit/core": "22.1.6", + "@angular-devkit/schematics": "22.1.6", + "jsonc-parser": "3.3.1", + "typescript": "6.0.3" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "node": "^22.22.3 || ^24.15.0 || >=26.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, - "node_modules/@sigstore/bundle": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/@sigstore/bundle/-/bundle-4.0.0.tgz", - "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.5.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@sigstore/core": { - "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/@sigstore/core/-/core-3.2.1.tgz", - "integrity": "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@sigstore/protobuf-specs": { - "version": "0.5.2", - "resolved": "https://registry.npmmirror.com/@sigstore/protobuf-specs/-/protobuf-specs-0.5.2.tgz", - "integrity": "sha512-SQqvFMt4V78fdjcDdYX6HbiVSOR4QK3ZgwCa2KOsopAgPIHy1rU5UDUmzLl02r5oyyaYcYHR1hpwDRk/yUe+Mw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@sigstore/sign": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/@sigstore/sign/-/sign-4.1.1.tgz", - "integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@gar/promise-retry": "^1.0.2", - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.2.0", - "@sigstore/protobuf-specs": "^0.5.0", - "make-fetch-happen": "^15.0.4", - "proc-log": "^6.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@sigstore/tuf": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/@sigstore/tuf/-/tuf-4.0.2.tgz", - "integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.5.0", - "tuf-js": "^4.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@sigstore/verify": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/@sigstore/verify/-/verify-3.1.1.tgz", - "integrity": "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.2.1", - "@sigstore/protobuf-specs": "^0.5.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "license": "MIT" }, - "node_modules/@tufjs/canonical-json": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", - "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@tufjs/models": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/@tufjs/models/-/models-4.1.0.tgz", - "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^10.1.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/@tybys/wasm-util": { "version": "0.10.3", "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", @@ -4381,22 +5102,12 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/dompurify": { - "version": "3.0.5", - "resolved": "https://registry.npmmirror.com/@types/dompurify/-/dompurify-3.0.5.tgz", - "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", - "license": "MIT", - "dependencies": { - "@types/trusted-types": "*" - } - }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmmirror.com/@types/esrecurse/-/esrecurse-4.3.1.tgz", "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.9", @@ -4405,6 +5116,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/gensync": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/@types/gensync/-/gensync-1.0.5.tgz", + "integrity": "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/howler": { "version": "2.2.13", "resolved": "https://registry.npmmirror.com/@types/howler/-/howler-2.2.13.tgz", @@ -4412,6 +5130,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -4433,13 +5158,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/marked": { - "version": "5.0.2", - "resolved": "https://registry.npmmirror.com/@types/marked/-/marked-5.0.2.tgz", - "integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "22.20.1", "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.20.1.tgz", @@ -4454,7 +5172,8 @@ "version": "2.0.7", "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.67.0", @@ -4690,16 +5409,16 @@ } }, "node_modules/@vitejs/plugin-basic-ssl": { - "version": "2.1.4", - "resolved": "https://registry.npmmirror.com/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.4.tgz", - "integrity": "sha512-HXciTXN/sDBYWgeAD4V4s0DN0g72x5mlxQhHxtYu3Tt8BLa6MzcJZUyDVFCdtjNs3bfENVHVzOsmooTVuNgAAw==", + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.3.0.tgz", + "integrity": "sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ==", "dev": true, "license": "MIT", "engines": { "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/@vitest/expect": { @@ -4822,23 +5541,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/abbrev": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-4.0.0.tgz", - "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz", @@ -4896,9 +5598,9 @@ } }, "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", "dependencies": { @@ -4930,36 +5632,10 @@ } } }, - "node_modules/algoliasearch": { - "version": "5.48.1", - "resolved": "https://registry.npmmirror.com/algoliasearch/-/algoliasearch-5.48.1.tgz", - "integrity": "sha512-Rf7xmeuIo7nb6S4mp4abW2faW8DauZyE2faBIKFaUfP3wnpOvNSbiI5AwVhqBNj0jPgBWEvhyCu0sLjN2q77Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/abtesting": "1.14.1", - "@algolia/client-abtesting": "5.48.1", - "@algolia/client-analytics": "5.48.1", - "@algolia/client-common": "5.48.1", - "@algolia/client-insights": "5.48.1", - "@algolia/client-personalization": "5.48.1", - "@algolia/client-query-suggestions": "5.48.1", - "@algolia/client-search": "5.48.1", - "@algolia/ingestion": "1.48.1", - "@algolia/monitoring": "1.48.1", - "@algolia/recommend": "5.48.1", - "@algolia/requester-browser-xhr": "5.48.1", - "@algolia/requester-fetch": "5.48.1", - "@algolia/requester-node-http": "5.48.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, "node_modules/angular-auth-oidc-client": { - "version": "21.0.3", - "resolved": "https://registry.npmmirror.com/angular-auth-oidc-client/-/angular-auth-oidc-client-21.0.3.tgz", - "integrity": "sha512-aXCSeKDxzAl7iEtVfm3y6OOewtObjoUk34cSzN+FbgT8ItfSwPxQwq5MPfBhBazDuMHeu4qlsjpEXWPG6ldGYw==", + "version": "22.0.0", + "resolved": "https://registry.npmmirror.com/angular-auth-oidc-client/-/angular-auth-oidc-client-22.0.0.tgz", + "integrity": "sha512-df+WgJ+1vLJCZMUCGcru+33JGdnupIny8NUDhmEqocEqhGgs0O3Lxj9sAPiMf0DVJ0ETFTT6zFrQ3YkRjZbFFA==", "license": "MIT", "dependencies": { "rfc4648": "^1.5.0", @@ -4972,6 +5648,30 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/angular-eslint": { + "version": "22.1.0", + "resolved": "https://registry.npmmirror.com/angular-eslint/-/angular-eslint-22.1.0.tgz", + "integrity": "sha512-LU5a6MOSeQTETnc4xaHi10p9S1cstNczr93sChi3rx8IzgH4+risIsjjQHVLSnQDrSGH2O8jAGCaChker7f7TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": ">= 22.0.0 < 23.0.0", + "@angular-devkit/schematics": ">= 22.0.0 < 23.0.0", + "@angular-eslint/builder": "22.1.0", + "@angular-eslint/eslint-plugin": "22.1.0", + "@angular-eslint/eslint-plugin-template": "22.1.0", + "@angular-eslint/schematics": "22.1.0", + "@angular-eslint/template-parser": "22.1.0", + "@typescript-eslint/types": "^8.0.0", + "@typescript-eslint/utils": "^8.0.0" + }, + "peerDependencies": { + "@angular/cli": ">= 22.0.0 < 23.0.0", + "eslint": "^9.0.0 || ^10.0.0", + "typescript": "*", + "typescript-eslint": "^8.0.0" + } + }, "node_modules/ansi-escapes": { "version": "7.3.0", "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-7.3.0.tgz", @@ -5262,9 +5962,9 @@ } }, "node_modules/beasties": { - "version": "0.4.1", - "resolved": "https://registry.npmmirror.com/beasties/-/beasties-0.4.1.tgz", - "integrity": "sha512-2Imdcw3LznDuxAbJM26RHniOLAzE6WgrK8OuvVXCQtNBS8rsnD9zsSEa3fHl4hHpUY7BYTlrpvtPVbvu9G6neg==", + "version": "0.4.3", + "resolved": "https://registry.npmmirror.com/beasties/-/beasties-0.4.3.tgz", + "integrity": "sha512-fIIeLOcbAB/K1kb1HBVJoiq1alHL4RCYBSo5e7HzrNkkgMggXR1Vqt/Z9JWnkfe/qdCo66Ux3QRwZioAIBdWRA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5416,38 +6116,6 @@ "node": ">= 0.8" } }, - "node_modules/cacache": { - "version": "20.0.4", - "resolved": "https://registry.npmmirror.com/cacache/-/cacache-20.0.4.tgz", - "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^5.0.0", - "fs-minipass": "^3.0.0", - "glob": "^13.0.0", - "lru-cache": "^11.1.0", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^13.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.9.tgz", @@ -5587,16 +6255,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -5627,17 +6285,17 @@ } }, "node_modules/cli-truncate": { - "version": "5.2.0", - "resolved": "https://registry.npmmirror.com/cli-truncate/-/cli-truncate-5.2.0.tgz", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "version": "6.1.1", + "resolved": "https://registry.npmmirror.com/cli-truncate/-/cli-truncate-6.1.1.tgz", + "integrity": "sha512-06p9vyLahLa4zkGcgsGxU6iEkSOiuI4fhCH6Emhe2lPAcoUv73n72DnODsnHA+5wwXGnV0n9M9/qOQJSjYhFhw==", "dev": true, "license": "MIT", "dependencies": { - "slice-ansi": "^8.0.0", + "slice-ansi": "^9.0.0", "string-width": "^8.2.0" }, "engines": { - "node": ">=20" + "node": ">=22" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5737,13 +6395,6 @@ "dev": true, "license": "MIT" }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz", @@ -6088,7 +6739,6 @@ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -6222,6 +6872,16 @@ "dev": true, "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/empathic/-/empathic-2.0.1.tgz", + "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", @@ -6245,16 +6905,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/environment": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/environment/-/environment-1.1.0.tgz", @@ -6455,9 +7105,9 @@ } }, "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6468,32 +7118,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "node_modules/escalade": { @@ -6758,7 +7408,6 @@ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", @@ -6977,13 +7626,6 @@ "node": ">= 0.6" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, - "license": "MIT" - }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmmirror.com/eventsource/-/eventsource-3.0.7.tgz", @@ -7013,16 +7655,9 @@ "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "dev": true, - "license": "Apache-2.0" + "engines": { + "node": ">=12.0.0" + } }, "node_modules/express": { "version": "5.2.1", @@ -7069,9 +7704,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.6.2", - "resolved": "https://registry.npmmirror.com/express-rate-limit/-/express-rate-limit-8.6.2.tgz", - "integrity": "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==", + "version": "8.7.0", + "resolved": "https://registry.npmmirror.com/express-rate-limit/-/express-rate-limit-8.7.0.tgz", + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", "dev": true, "license": "MIT", "dependencies": { @@ -7144,6 +7779,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, "node_modules/fast-uri": { "version": "3.1.6", "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.6.tgz", @@ -7161,6 +7813,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", @@ -7308,19 +7970,6 @@ "node": ">= 0.8" } }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", @@ -7478,24 +8127,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmmirror.com/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", @@ -7508,13 +8139,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmmirror.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz", @@ -7659,9 +8283,9 @@ } }, "node_modules/hono": { - "version": "4.13.3", - "resolved": "https://registry.npmmirror.com/hono/-/hono-4.13.3.tgz", - "integrity": "sha512-r8AO2mYHoLxSHkgafNeC/BXyb2vWRxD3jem4Ts+ptav8oTG5FIRifAjuJEmZI4bSvvc2ns0GxmIYiZnHqN3mMw==", + "version": "4.13.5", + "resolved": "https://registry.npmmirror.com/hono/-/hono-4.13.5.tgz", + "integrity": "sha512-O6+/eCYRkzzzy0rPWwKLiGBR1nFuUPZynnwjxN1MBA62NNqbT0wQEzQyK2gSO5yDIDB336sXQleAhOHrzlYyKw==", "dev": true, "license": "MIT", "engines": { @@ -7669,16 +8293,16 @@ } }, "node_modules/hosted-git-info": { - "version": "9.0.3", - "resolved": "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-9.0.3.tgz", - "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "version": "10.1.1", + "resolved": "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-10.1.1.tgz", + "integrity": "sha512-DeOnSPAvOndYKfw075gt8yZzQ7S2hNztw34zBTfhIzLhmBTswIBg5/y+pqu/VD5cYWm5goAFTusDmUEmKZ0PEQ==", "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^11.1.0" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, "node_modules/hosted-git-info/node_modules/lru-cache": { @@ -7743,13 +8367,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "dev": true, - "license": "BSD-2-Clause" - }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", @@ -7826,19 +8443,6 @@ "node": ">= 4" } }, - "node_modules/ignore-walk": { - "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/ignore-walk/-/ignore-walk-8.0.0.tgz", - "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", - "dev": true, - "license": "ISC", - "dependencies": { - "minimatch": "^10.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/immutable": { "version": "5.1.9", "resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.9.tgz", @@ -7862,6 +8466,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -7879,16 +8494,6 @@ "dev": true, "license": "ISC" }, - "node_modules/ini": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/ini/-/ini-6.0.0.tgz", - "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.1.0.tgz", @@ -7905,9 +8510,9 @@ } }, "node_modules/ip-address": { - "version": "10.5.0", - "resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.5.0.tgz", - "integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==", + "version": "10.7.0", + "resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.7.0.tgz", + "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", "dev": true, "license": "MIT", "engines": { @@ -8422,6 +9027,8 @@ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -8432,6 +9039,8 @@ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", @@ -8576,16 +9185,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-parse-even-better-errors": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", - "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -8627,16 +9226,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "license": "MIT" - }, "node_modules/katex": { "version": "0.16.47", "resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.47.tgz", @@ -8663,18 +9252,279 @@ "json-buffer": "3.0.1" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/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/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lilconfig": { @@ -8696,76 +9546,24 @@ "license": "MIT" }, "node_modules/listr2": { - "version": "9.0.5", - "resolved": "https://registry.npmmirror.com/listr2/-/listr2-9.0.5.tgz", - "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^5.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/listr2/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/listr2/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "version": "11.0.0", + "resolved": "https://registry.npmmirror.com/listr2/-/listr2-11.0.0.tgz", + "integrity": "sha512-8K88S0aSrcSXdJfiZtEy5BQMnR+TyjrCGLcgAvQs6ta0NEnIm0RJ72/Pv67Jvg07cfBhDbuN74V81lSSVYEFEw==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" + "cli-truncate": "^6.1.1", + "log-update": "^8.0.0", + "wrap-ansi": "^10.0.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=22.13.0" } }, "node_modules/lmdb": { - "version": "3.5.1", - "resolved": "https://registry.npmmirror.com/lmdb/-/lmdb-3.5.1.tgz", - "integrity": "sha512-NYHA0MRPjvNX+vSw8Xxg6FLKxzAG+e7Pt8RqAQA/EehzHVXq9SxDqJIN3JL1hK0dweb884y8kIh6rkWvPyg9Wg==", + "version": "3.5.6", + "resolved": "https://registry.npmmirror.com/lmdb/-/lmdb-3.5.6.tgz", + "integrity": "sha512-j3uE8ReKNyUWDjhfEFSJqE/1DLtfTR5Z8yFzVHvBjAk37wNg7HdScjcv8ttPHRvrdgPQMPWxFFI0SsdBzI5lBw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -8782,13 +9580,13 @@ "download-lmdb-prebuilds": "bin/download-prebuilds.js" }, "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "3.5.1", - "@lmdb/lmdb-darwin-x64": "3.5.1", - "@lmdb/lmdb-linux-arm": "3.5.1", - "@lmdb/lmdb-linux-arm64": "3.5.1", - "@lmdb/lmdb-linux-x64": "3.5.1", - "@lmdb/lmdb-win32-arm64": "3.5.1", - "@lmdb/lmdb-win32-x64": "3.5.1" + "@lmdb/lmdb-darwin-arm64": "3.5.6", + "@lmdb/lmdb-darwin-x64": "3.5.6", + "@lmdb/lmdb-linux-arm": "3.5.6", + "@lmdb/lmdb-linux-arm64": "3.5.6", + "@lmdb/lmdb-linux-x64": "3.5.6", + "@lmdb/lmdb-win32-arm64": "3.5.6", + "@lmdb/lmdb-win32-x64": "3.5.6" } }, "node_modules/locate-path": { @@ -8832,97 +9630,34 @@ } }, "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/log-update/-/log-update-8.0.0.tgz", + "integrity": "sha512-lddSgOt3bPASrylL54ZSpy8nBHns+vBVSoILlVOx+dei300pnLRN958rj/EdlVLKuWlSESU3qdnDZdAI7FXYGg==", "dev": true, "license": "MIT", "dependencies": { - "ansi-escapes": "^7.0.0", + "ansi-escapes": "^7.3.0", "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" + "slice-ansi": "^9.0.0", + "string-width": "^8.2.0", + "strip-ansi": "^7.2.0", + "wrap-ansi": "^10.0.0" }, "engines": { - "node": ">=18" + "node": ">=22" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", + "optional": true, + "peer": true, "dependencies": { "yallist": "^3.0.2" } @@ -8937,30 +9672,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/make-fetch-happen": { - "version": "15.0.6", - "resolved": "https://registry.npmmirror.com/make-fetch-happen/-/make-fetch-happen-15.0.6.tgz", - "integrity": "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@gar/promise-retry": "^1.0.0", - "@npmcli/agent": "^4.0.0", - "@npmcli/redact": "^4.0.0", - "cacache": "^20.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^5.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^6.0.0", - "ssri": "^13.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/marked": { "version": "17.0.6", "resolved": "https://registry.npmmirror.com/marked/-/marked-17.0.6.tgz", @@ -9065,182 +9776,49 @@ }, "engines": { "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-collect": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-fetch": { - "version": "5.0.2", - "resolved": "https://registry.npmmirror.com/minipass-fetch/-/minipass-fetch-5.0.2.tgz", - "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^2.0.0", - "minizlib": "^3.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - }, - "optionalDependencies": { - "iconv-lite": "^0.7.2" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/minipass-flush/-/minipass-flush-1.0.7.tgz", - "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/minipass-sized": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/minipass-sized/-/minipass-sized-2.0.0.tgz", - "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "minipass": "^7.1.2" + "brace-expansion": "^5.0.8" }, "engines": { - "node": ">=8" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/mrmime": { @@ -9295,13 +9873,13 @@ } }, "node_modules/mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", "dev": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/mz": { @@ -9417,31 +9995,6 @@ "semver": "bin/semver.js" } }, - "node_modules/node-gyp": { - "version": "12.4.0", - "resolved": "https://registry.npmmirror.com/node-gyp/-/node-gyp-12.4.0.tgz", - "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "nopt": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "tar": "^7.5.4", - "tinyglobby": "^0.2.12", - "undici": "^6.25.0", - "which": "^6.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/node-gyp-build-optional-packages": { "version": "5.2.2", "resolved": "https://registry.npmmirror.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", @@ -9458,32 +10011,6 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, - "node_modules/node-gyp/node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/node-releases": { "version": "2.0.53", "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.53.tgz", @@ -9494,22 +10021,6 @@ "node": ">=18" } }, - "node_modules/nopt": { - "version": "9.0.0", - "resolved": "https://registry.npmmirror.com/nopt/-/nopt-9.0.0.tgz", - "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "^4.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9519,106 +10030,20 @@ "node": ">=0.10.0" } }, - "node_modules/npm-bundled": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/npm-bundled/-/npm-bundled-5.0.0.tgz", - "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", - "dev": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^5.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-install-checks": { - "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/npm-install-checks/-/npm-install-checks-8.0.0.tgz", - "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-normalize-package-bin": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", - "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/npm-package-arg": { - "version": "13.0.2", - "resolved": "https://registry.npmmirror.com/npm-package-arg/-/npm-package-arg-13.0.2.tgz", - "integrity": "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==", + "version": "14.0.0", + "resolved": "https://registry.npmmirror.com/npm-package-arg/-/npm-package-arg-14.0.0.tgz", + "integrity": "sha512-69XQh3k+dtGa1p+7RaR57IuG3rCko96xr/nUfN4yDYBXbTYICiWcOpsFKLN2GtGE9cyIljE+f1exnaYt9MvM+Q==", "dev": true, "license": "ISC", "dependencies": { - "hosted-git-info": "^9.0.0", - "proc-log": "^6.0.0", + "hosted-git-info": "^10.1.0", + "proc-log": "^7.0.0", "semver": "^7.3.5", - "validate-npm-package-name": "^7.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-packlist": { - "version": "10.0.4", - "resolved": "https://registry.npmmirror.com/npm-packlist/-/npm-packlist-10.0.4.tgz", - "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", - "dev": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^8.0.0", - "proc-log": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-pick-manifest": { - "version": "11.0.3", - "resolved": "https://registry.npmmirror.com/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", - "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "npm-install-checks": "^8.0.0", - "npm-normalize-package-bin": "^5.0.0", - "npm-package-arg": "^13.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-registry-fetch": { - "version": "19.1.1", - "resolved": "https://registry.npmmirror.com/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", - "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/redact": "^4.0.0", - "jsonparse": "^1.3.1", - "make-fetch-happen": "^15.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^5.0.0", - "minizlib": "^3.0.1", - "npm-package-arg": "^13.0.0", - "proc-log": "^6.0.0" + "validate-npm-package-name": "^8.0.0" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, "node_modules/nth-check": { @@ -9844,9 +10269,9 @@ } }, "node_modules/ora": { - "version": "9.3.0", - "resolved": "https://registry.npmmirror.com/ora/-/ora-9.3.0.tgz", - "integrity": "sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==", + "version": "9.4.1", + "resolved": "https://registry.npmmirror.com/ora/-/ora-9.4.1.tgz", + "integrity": "sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==", "dev": true, "license": "MIT", "dependencies": { @@ -9856,7 +10281,7 @@ "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", - "stdin-discarder": "^0.3.1", + "stdin-discarder": "^0.3.2", "string-width": "^8.1.0" }, "engines": { @@ -9906,6 +10331,44 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oxc-parser": { + "version": "0.142.0", + "resolved": "https://registry.npmmirror.com/oxc-parser/-/oxc-parser-0.142.0.tgz", + "integrity": "sha512-kKR+jPiRJYJDexVoziIg/FVGvr1fT1FZSSJOk6tVoMKKSlsf1Cso+cgGCJkOEDWOP174vRntCPFKg+AS7InWvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.142.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.142.0", + "@oxc-parser/binding-android-arm64": "0.142.0", + "@oxc-parser/binding-darwin-arm64": "0.142.0", + "@oxc-parser/binding-darwin-x64": "0.142.0", + "@oxc-parser/binding-freebsd-x64": "0.142.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.142.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.142.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.142.0", + "@oxc-parser/binding-linux-arm64-musl": "0.142.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.142.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.142.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.142.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.142.0", + "@oxc-parser/binding-linux-x64-gnu": "0.142.0", + "@oxc-parser/binding-linux-x64-musl": "0.142.0", + "@oxc-parser/binding-openharmony-arm64": "0.142.0", + "@oxc-parser/binding-wasm32-wasi": "0.142.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.142.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.142.0", + "@oxc-parser/binding-win32-x64-msvc": "0.142.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", @@ -9938,51 +10401,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "7.0.6", - "resolved": "https://registry.npmmirror.com/p-map/-/p-map-7.0.6.tgz", - "integrity": "sha512-I4Prw6ivkd6p8PiYR1tXASOAOBzIJwu0TB7fqaX0c/8c3QAehNYmX57EijyGGGBt3c/BIowGwV03RVBtXvHEVg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pacote": { - "version": "21.5.1", - "resolved": "https://registry.npmmirror.com/pacote/-/pacote-21.5.1.tgz", - "integrity": "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@gar/promise-retry": "^1.0.0", - "@npmcli/git": "^7.0.0", - "@npmcli/installed-package-contents": "^4.0.0", - "@npmcli/package-json": "^7.0.0", - "@npmcli/promise-spawn": "^9.0.0", - "@npmcli/run-script": "^10.0.0", - "cacache": "^20.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^13.0.0", - "npm-packlist": "^10.0.1", - "npm-pick-manifest": "^11.0.1", - "npm-registry-fetch": "^19.0.0", - "proc-log": "^6.0.0", - "sigstore": "^4.0.0", - "ssri": "^13.0.0", - "tar": "^7.4.3" - }, - "bin": { - "pacote": "bin/index.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", @@ -10032,13 +10450,13 @@ } }, "node_modules/parse5-html-rewriting-stream": { - "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.0.tgz", - "integrity": "sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==", + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.1.tgz", + "integrity": "sha512-NaRku2aMpUN1Sh1Gyk1KWUh2A7EJx2c6qYzvwsPtqhoHoaURshdrceYK3LunVCm3WHhm6FS7Vcczbvdh3/UIVw==", "dev": true, "license": "MIT", "dependencies": { - "entities": "^6.0.0", + "entities": "^8.0.0", "parse5": "^8.0.0", "parse5-sax-parser": "^8.0.0" }, @@ -10047,13 +10465,13 @@ } }, "node_modules/parse5-html-rewriting-stream/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", "dev": true, "license": "BSD-2-Clause", "engines": { - "node": ">=0.12" + "node": ">=20.19.0" }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" @@ -10120,33 +10538,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, - "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/path-to-regexp": { "version": "8.4.2", "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.4.2.tgz", @@ -10181,9 +10572,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { "node": ">=12" @@ -10485,13 +10876,13 @@ } }, "node_modules/proc-log": { - "version": "6.1.0", - "resolved": "https://registry.npmmirror.com/proc-log/-/proc-log-6.1.0.tgz", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/proc-log/-/proc-log-7.0.0.tgz", + "integrity": "sha512-FYgfaA69XZ93zaXLoMNQ+ViDXGGBgR8aLh03txzcFhV+9xOXx7+8DLCULrKKpR9+GsH9ZfHm82aSUPpozX0Ztg==", "dev": true, "license": "ISC", "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, "node_modules/proxy-addr": { @@ -10508,6 +10899,24 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent-negotiate": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz", + "integrity": "sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "kerberos": "^2.0.0" + }, + "peerDependenciesMeta": { + "kerberos": { + "optional": true + } + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", @@ -10519,9 +10928,9 @@ } }, "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmmirror.com/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "version": "6.16.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -10732,22 +11141,15 @@ "integrity": "sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==", "license": "MIT" }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, "node_modules/rolldown": { - "version": "1.0.0-rc.4", - "resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.0.0-rc.4.tgz", - "integrity": "sha512-V2tPDUrY3WSevrvU2E41ijZlpF+5PbZu4giH+VpNraaadsJGHa4fR6IFwsocVwEXDoAdIv5qgPPxgrvKAOIPtA==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.2.0.tgz", + "integrity": "sha512-u7tgm5l4Yw1iTqUL4EcYOAt7fFvCgQMLeidrnD4GALlC6aOznCjezYajgxeyKw27u0Q5N7fwgCzjVyPIWzwuBA==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.113.0", - "@rolldown/pluginutils": "1.0.0-rc.4" + "@oxc-project/types": "=0.140.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -10756,19 +11158,31 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.4", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.4", - "@rolldown/binding-darwin-x64": "1.0.0-rc.4", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.4", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.4", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.4", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.4", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.4", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.4", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.4", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.4", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.4", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.4" + "@rolldown/binding-android-arm64": "1.2.0", + "@rolldown/binding-darwin-arm64": "1.2.0", + "@rolldown/binding-darwin-x64": "1.2.0", + "@rolldown/binding-freebsd-x64": "1.2.0", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.0", + "@rolldown/binding-linux-arm64-gnu": "1.2.0", + "@rolldown/binding-linux-arm64-musl": "1.2.0", + "@rolldown/binding-linux-ppc64-gnu": "1.2.0", + "@rolldown/binding-linux-s390x-gnu": "1.2.0", + "@rolldown/binding-linux-x64-gnu": "1.2.0", + "@rolldown/binding-linux-x64-musl": "1.2.0", + "@rolldown/binding-openharmony-arm64": "1.2.0", + "@rolldown/binding-wasm32-wasi": "1.2.0", + "@rolldown/binding-win32-arm64-msvc": "1.2.0", + "@rolldown/binding-win32-x64-msvc": "1.2.0" + } + }, + "node_modules/rolldown/node_modules/@oxc-project/types": { + "version": "0.140.0", + "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.140.0.tgz", + "integrity": "sha512-h5LUOzGArYemnW1NMz/DuuQhBi96J6JL2Bk8zE4kvqxB5Sg3jxmCiH4uyOWHDkiKSt5vWlG4FIwCR/DbstcNRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, "node_modules/rollup": { @@ -10777,6 +11191,8 @@ "integrity": "sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@types/estree": "1.0.9" }, @@ -10935,55 +11351,25 @@ "dev": true, "license": "MIT" }, - "node_modules/sass": { - "version": "1.97.3", - "resolved": "https://registry.npmmirror.com/sass/-/sass-1.97.3.tgz", - "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^4.0.0", - "immutable": "^5.0.2", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "@parcel/watcher": "^2.4.1" - } - }, - "node_modules/sass/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/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/sass/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "node_modules/sass": { + "version": "1.101.0", + "resolved": "https://registry.npmmirror.com/sass/-/sass-1.101.0.tgz", + "integrity": "sha512-OL3GoQyoUdDt843DpVmDO6y2k1sc5IhUDSpu8XucEI+35neq5QivZ1iuegnpraEVTJXlQGK1gl27zKcTLEPbQw==", "dev": true, "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, "engines": { - "node": ">= 14.18.0" + "node": ">=20.19.0" }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, "node_modules/saxes": { @@ -11000,9 +11386,9 @@ } }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -11234,28 +11620,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/sigstore": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/sigstore/-/sigstore-4.1.1.tgz", - "integrity": "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.2.1", - "@sigstore/protobuf-specs": "^0.5.0", - "@sigstore/sign": "^4.1.1", - "@sigstore/tuf": "^4.0.2", - "@sigstore/verify": "^3.1.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/slice-ansi": { - "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-8.0.0.tgz", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "version": "9.0.0", + "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-9.0.0.tgz", + "integrity": "sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==", "dev": true, "license": "MIT", "dependencies": { @@ -11263,7 +11631,7 @@ "is-fullwidth-code-point": "^5.1.0" }, "engines": { - "node": ">=20" + "node": ">=22" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" @@ -11282,47 +11650,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.9", - "resolved": "https://registry.npmmirror.com/socks/-/socks-2.8.9.tgz", - "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.1.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.7.6.tgz", @@ -11363,164 +11690,362 @@ "node": ">=0.10.0" } }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmmirror.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, - "license": "CC-BY-3.0" + "license": "MIT" }, - "node_modules/spdx-expression-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stdin-discarder": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/stdin-discarder/-/stdin-discarder-0.3.2.tgz", + "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "dev": true, "license": "MIT", "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmmirror.com/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", "dev": true, - "license": "CC0-1.0" + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/ssri": { - "version": "13.0.1", - "resolved": "https://registry.npmmirror.com/ssri/-/ssri-13.0.1.tgz", - "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^7.0.3" + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/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/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/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/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/std-env": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/std-env/-/std-env-4.2.0.tgz", - "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true, "license": "MIT" }, - "node_modules/stdin-discarder": { - "version": "0.3.2", - "resolved": "https://registry.npmmirror.com/stdin-discarder/-/stdin-discarder-0.3.2.tgz", - "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", + "node_modules/synckit": { + "version": "0.11.13", + "resolved": "https://registry.npmmirror.com/synckit/-/synckit-0.11.13.tgz", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", "dev": true, "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.3.6" + }, "engines": { - "node": ">=18" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/synckit" } }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" }, "engines": { - "node": ">= 0.4" + "node": ">=14.0.0" } }, - "node_modules/string-width": { - "version": "8.2.2", - "resolved": "https://registry.npmmirror.com/string-width/-/string-width-8.2.2.tgz", - "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", - "dev": true, + "node_modules/tailwindcss/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "license": "MIT", "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">=20" + "node": ">= 8.10.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.11", - "resolved": "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", - "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", - "dev": true, - "license": "MIT", + "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.2", - "es-object-atoms": "^1.1.2", - "has-property-descriptors": "^1.0.2", - "safe-regex-test": "^1.1.0" + "is-glob": "^4.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/string.prototype.trimend": { - "version": "1.0.10", - "resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", - "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", - "dev": true, + "node_modules/tailwindcss/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.2" + "picomatch": "^2.2.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8.10.0" } }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, + "node_modules/tailwindcss/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { "node": ">= 0.4" @@ -11529,234 +12054,284 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", "dev": true, "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", "dependencies": { - "ansi-regex": "^6.2.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=12" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=14.0.0" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmmirror.com/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "tldts-core": "^6.1.86" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "tldts": "bin/cli.js" } }, - "node_modules/sucrase": { - "version": "3.35.1", - "resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmmirror.com/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" + "is-number": "^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=8.0" } }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=0.6" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=18.12" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "typescript": ">=4.8.4" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" }, - "node_modules/synckit": { - "version": "0.11.13", - "resolved": "https://registry.npmmirror.com/synckit/-/synckit-0.11.13.tgz", - "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmmirror.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.3.6" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" } }, - "node_modules/tailwindcss": { - "version": "3.4.19", - "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.19.tgz", - "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, "license": "MIT", "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.7", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" + "minimist": "^1.2.0" }, "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" + "json5": "lib/cli.js" } }, - "node_modules/tailwindcss/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "prelude-ls": "^1.2.1" }, "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": ">= 0.8.0" } }, - "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "dev": true, + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { - "node": ">= 6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/tailwindcss/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/tailwindcss/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" }, "engines": { - "node": ">=8.10.0" + "node": ">= 0.4" } }, - "node_modules/tailwindcss/node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" }, - "bin": { - "resolve": "bin/resolve" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" }, "engines": { "node": ">= 0.4" @@ -11765,579 +12340,585 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tar": { - "version": "7.5.22", - "resolved": "https://registry.npmmirror.com/tar/-/tar-7.5.22.tgz", - "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, "engines": { - "node": ">=18" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" + "node": ">=14.17" } }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "node_modules/typescript-eslint": { + "version": "8.67.0", + "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.67.0.tgz", + "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==", + "dev": true, "license": "MIT", "dependencies": { - "thenify": ">= 3.1.0 < 4" + "@typescript-eslint/eslint-plugin": "8.67.0", + "@typescript-eslint/parser": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0" }, "engines": { - "node": ">=0.8" + "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/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.3.0.tgz", - "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" }, "engines": { - "node": ">=12.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tinyrainbow": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-3.1.1.tgz", - "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">= 0.8" } }, - "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmmirror.com/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "node_modules/update-browserslist-db": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "tldts-core": "^6.1.86" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { - "tldts": "bin/cli.js" + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmmirror.com/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "MIT" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" + "punycode": "^2.1.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, - "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "node_modules/validate-npm-package-name": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/validate-npm-package-name/-/validate-npm-package-name-8.0.0.tgz", + "integrity": "sha512-SCv6OOV6Xj2/3cXy3dGmADluJTNcL3o7hZAglNPTe+WYuEuvxgJzxPrSDLZhF+CwyQOubqgecjMmTJGMVLWjYQ==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^6.1.32" - }, + "license": "ISC", "engines": { - "node": ">=16" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, - "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmmirror.com/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, "engines": { - "node": ">=18" + "node": ">= 0.8" } }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmmirror.com/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "dev": true, "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">=18.12" + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" }, "peerDependencies": { - "typescript": ">=4.8.4" + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "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/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "license": "Apache-2.0" - }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmmirror.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "node_modules/vite/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" } }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "node_modules/vite/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" + "tslib": "^2.4.0" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tuf-js": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/tuf-js/-/tuf-js-4.1.0.tgz", - "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", + "node_modules/vite/node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", "dev": true, "license": "MIT", - "dependencies": { - "@tufjs/models": "4.1.0", - "debug": "^4.4.3", - "make-fetch-happen": "^15.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "node_modules/vite/node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.8.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "node_modules/vite/node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "node_modules/vite/node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "node_modules/vite/node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "node_modules/vite/node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/typed-array-length": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.8.tgz", - "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.9", - "for-each": "^0.3.5", - "gopd": "^1.2.0", - "is-typed-array": "^1.1.15", - "possible-typed-array-names": "^1.1.0", - "reflect.getprototypeof": "^1.0.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14.17" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/typescript-eslint": { - "version": "8.67.0", - "resolved": "https://registry.npmmirror.com/typescript-eslint/-/typescript-eslint-8.67.0.tgz", - "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==", + "node_modules/vite/node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.67.0", - "@typescript-eslint/parser": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/utils": "8.67.0" - }, + "optional": true, + "os": [ + "linux" + ], "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": "^20.19.0 || >=22.12.0" } }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "node_modules/vite/node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/undici": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/undici/-/undici-7.29.0.tgz", - "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "node_modules/vite/node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=20.18.1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "node_modules/vite/node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/update-browserslist-db": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", - "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "node_modules/vite/node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" ], + "dev": true, "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "node_modules/vite/node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "optional": true, "dependencies": { - "punycode": "^2.1.0" + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/validate-npm-package-name": { - "version": "7.0.2", - "resolved": "https://registry.npmmirror.com/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", - "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "node_modules/vite/node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/vite/node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/vite": { - "version": "7.3.6", - "resolved": "https://registry.npmmirror.com/vite/-/vite-7.3.6.tgz", - "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "node_modules/vite/node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.27.0 || ^0.28.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "vite": "bin/vite.js" + "rolldown": "bin/cli.mjs" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, "optionalDependencies": { - "fsevents": "~2.3.3" + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/vite/node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" + "engines": { + "node": ">=12.0.0" }, - "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 - } + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/vitest": { @@ -12444,13 +13025,12 @@ } }, "node_modules/watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", "dev": true, "license": "MIT", "dependencies": { - "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" }, "engines": { @@ -12659,73 +13239,33 @@ } }, "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "10.0.1", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-10.0.1.tgz", + "integrity": "sha512-M0N4xzyzosiIok3svYlEo1sdLZts/8FPgYH/GPC3wvlmPoRvnoManGMrE54waYj3tISA8w6lsdesfVv67qSr8Q==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.3", + "string-width": "^8.2.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "node": ">=20" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/wrappy": { @@ -12789,19 +13329,21 @@ "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/yargs": { - "version": "18.0.0", - "resolved": "https://registry.npmmirror.com/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "version": "18.1.0", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", "dev": true, "license": "MIT", "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", + "string-width": "^8.2.1", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" }, @@ -12819,24 +13361,6 @@ "node": "^20.19.0 || ^22.12.0 || >=23" } }, - "node_modules/yargs/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -12863,24 +13387,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", - "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "dev": true, + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 847d79d..b08c724 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "knowledgebuilder", - "version": "1.9.0", + "version": "1.9.1", "license": "MIT", "description": "An AI-powered web-based Learning app for English, Chinese, and Knowledge Bank part.", "author": { @@ -23,20 +23,19 @@ }, "private": true, "dependencies": { - "@angular/animations": "21.2.21", - "@angular/cdk": "^21.2.14", - "@angular/common": "21.2.21", - "@angular/compiler": "21.2.21", - "@angular/core": "21.2.21", - "@angular/forms": "21.2.21", - "@angular/material": "^21.2.14", - "@angular/material-date-fns-adapter": "^21.2.14", - "@angular/platform-browser": "21.2.21", - "@angular/router": "21.2.21", + "@angular/animations": "22.1.4", + "@angular/cdk": "^22.1.4", + "@angular/common": "22.1.4", + "@angular/compiler": "22.1.4", + "@angular/core": "22.1.4", + "@angular/forms": "22.1.4", + "@angular/material": "^22.1.4", + "@angular/material-date-fns-adapter": "^22.1.4", + "@angular/platform-browser": "22.1.4", + "@angular/router": "22.1.4", "@jsverse/transloco": "^8.2.1", - "@types/dompurify": "^3.0.5", "actslib": "^0.6.81", - "angular-auth-oidc-client": "^21.0.2", + "angular-auth-oidc-client": "^22.0.0", "date-fns": "^4.1.0", "dompurify": "^3.4.11", "howler": "^2.2.4", @@ -49,44 +48,25 @@ "zone.js": "~0.15.0" }, "devDependencies": { - "@angular-eslint/builder": "^21.1.0", - "@angular-eslint/eslint-plugin": "^21.1.0", - "@angular-eslint/eslint-plugin-template": "^21.1.0", - "@angular/build": "21.2.21", - "@angular/cli": "21.2.21", - "@angular/compiler-cli": "21.2.21", - "@angular/platform-browser": "21.2.21", + "@angular-eslint/builder": "^22.1.0", + "@angular-eslint/eslint-plugin": "^22.1.0", + "@angular-eslint/eslint-plugin-template": "^22.1.0", + "@angular/build": "22.1.6", + "@angular/cli": "22.1.6", + "@angular/compiler-cli": "22.1.4", "@types/howler": "^2.2.11", "@types/katex": "^0.16.7", - "@types/marked": "^5.0.2", "@types/node": "^22.13.1", "@typescript-eslint/eslint-plugin": "^8.54.0", "@typescript-eslint/parser": "^8.54.0", + "angular-eslint": "^22.1.0", "eslint": "^9.39.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "^5.5.5", "jsdom": "^26.0.0", "prettier": "^3.8.1", - "typescript": "~5.9.3", + "typescript": "~6.0.3", "typescript-eslint": "^8.54.0", "vitest": "^4.0.0" - }, - "overrides": { - "@babel/core": "^7.29.7", - "fast-uri": "^3.1.5", - "flatted": "^3.4.2", - "hono": "^4.12.28", - "js-yaml": "^4.3.1", - "nanoid": "^3.3.18", - "postcss": "^8.5.18", - "rollup": "^4.62.2", - "sucrase": { - "glob": "^10.5.0" - }, - "yaml": "^2.9.0", - "undici": "^7.29.0", - "ip-address": "^10.3.1", - "brace-expansion@^1.0.0": "^1.1.16", - "brace-expansion@^2.0.0": "^2.1.4" } } diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index c7f4dcc..b8ca6fd 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { RouterOutlet } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; @@ -9,6 +9,7 @@ import { NavigationFocusService } from './shared/navigation-focus/navigation-foc @Component({ selector: 'app-navbar', template: '
Mock Navbar
', + changeDetection: ChangeDetectionStrategy.Eager, standalone: true, }) class MockNavbarComponent {} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 48ec4db..ff6db89 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,5 @@ import { MediaMatcher } from '@angular/cdk/layout'; -import { ChangeDetectorRef, inject } from '@angular/core'; +import { ChangeDetectorRef, inject, ChangeDetectionStrategy } from '@angular/core'; import { Component, ViewEncapsulation } from '@angular/core'; import { RouterOutlet, RouterModule } from '@angular/router'; @@ -10,6 +10,7 @@ import { NavbarComponent } from './shared/navbar/navbar'; imports: [RouterOutlet, RouterModule, NavbarComponent], encapsulation: ViewEncapsulation.None, templateUrl: './app.component.html', + changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './app.component.scss', }) export class AppComponent { diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 4251331..43cf0ea 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,4 +1,4 @@ -import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { provideHttpClient, withInterceptors, withXhr } from '@angular/common/http'; import type { ApplicationConfig } from '@angular/core'; import { provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; @@ -14,7 +14,7 @@ export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routeConfig), - provideHttpClient(withInterceptors([authInterceptor])), + provideHttpClient(withXhr(), withInterceptors([authInterceptor])), ...provideTranslocoStandalone(), provideAuth({ config: { diff --git a/src/app/pages/chinese-exercises/chinese-exercises-select-dialog.component.spec.ts b/src/app/pages/chinese-exercises/chinese-exercises-select-dialog.component.spec.ts index 7752c1a..bf4bed3 100644 --- a/src/app/pages/chinese-exercises/chinese-exercises-select-dialog.component.spec.ts +++ b/src/app/pages/chinese-exercises/chinese-exercises-select-dialog.component.spec.ts @@ -1,6 +1,6 @@ -import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClient, withXhr } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; -import type { ComponentFixture} from '@angular/core/testing'; +import type { ComponentFixture } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -43,7 +43,7 @@ describe('ChineseExercisesSelectDialogComponent', () => { { provide: TranslocoService, useValue: mockTranslocoService() }, { provide: TRANSLOCO_TRANSPILER, useValue: {} }, { provide: TRANSLOCO_MISSING_HANDLER, useValue: {} }, - provideHttpClient(), + provideHttpClient(withXhr()), provideHttpClientTesting(), ], }).compileComponents(); diff --git a/src/app/pages/english-listening/english-listening.component.spec.ts b/src/app/pages/english-listening/english-listening.component.spec.ts index 6c83213..87d6fbd 100644 --- a/src/app/pages/english-listening/english-listening.component.spec.ts +++ b/src/app/pages/english-listening/english-listening.component.spec.ts @@ -1,4 +1,4 @@ -import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClient, withXhr } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import type { ComponentFixture } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing'; @@ -135,7 +135,7 @@ describe('EnglishListeningComponent', () => { await TestBed.configureTestingModule({ imports: [EnglishListeningComponent, NoopAnimationsModule], providers: [ - provideHttpClient(), + provideHttpClient(withXhr()), provideHttpClientTesting(), { provide: LearningContentService, useValue: learningContentSpy }, { provide: AudioService, useValue: audioSpy }, diff --git a/src/app/pages/knowledge-exercises/knowledge-exercises-list/knowledge-exercises-select-dialog.component.spec.ts b/src/app/pages/knowledge-exercises/knowledge-exercises-list/knowledge-exercises-select-dialog.component.spec.ts index 8811baa..9147e05 100644 --- a/src/app/pages/knowledge-exercises/knowledge-exercises-list/knowledge-exercises-select-dialog.component.spec.ts +++ b/src/app/pages/knowledge-exercises/knowledge-exercises-list/knowledge-exercises-select-dialog.component.spec.ts @@ -1,6 +1,6 @@ -import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClient, withXhr } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; -import type { ComponentFixture} from '@angular/core/testing'; +import type { ComponentFixture } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -48,7 +48,7 @@ describe('KnowledgeExercisesSelectDialogComponent', () => { { provide: TranslocoService, useValue: mockTranslocoService() }, { provide: TRANSLOCO_TRANSPILER, useValue: {} }, { provide: TRANSLOCO_MISSING_HANDLER, useValue: {} }, - provideHttpClient(), + provideHttpClient(withXhr()), provideHttpClientTesting(), ], }).compileComponents(); diff --git a/src/app/pages/translate-exercises/translate-exercises-info-dialog.component.ts b/src/app/pages/translate-exercises/translate-exercises-info-dialog.component.ts index 547e091..5d1c894 100644 --- a/src/app/pages/translate-exercises/translate-exercises-info-dialog.component.ts +++ b/src/app/pages/translate-exercises/translate-exercises-info-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, inject } from '@angular/core'; +import { Component, Inject, inject, ChangeDetectionStrategy } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DATA, @@ -14,6 +14,7 @@ import { MarkdownContentComponent } from '../../shared/markdown-content'; @Component({ selector: 'app-translate-exercises-info-dlg', templateUrl: './translate-exercises-info-dialog.html', + changeDetection: ChangeDetectionStrategy.Eager, imports: [ MatButtonModule, MatDialogTitle, diff --git a/src/app/pages/translate-exercises/translate-exercises-llm-dialog.component.ts b/src/app/pages/translate-exercises/translate-exercises-llm-dialog.component.ts index 6dce5e4..e337513 100644 --- a/src/app/pages/translate-exercises/translate-exercises-llm-dialog.component.ts +++ b/src/app/pages/translate-exercises/translate-exercises-llm-dialog.component.ts @@ -1,4 +1,11 @@ -import { Component, Inject, DestroyRef, inject, model } from '@angular/core'; +import { + Component, + Inject, + DestroyRef, + inject, + model, + ChangeDetectionStrategy, +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; @@ -20,6 +27,7 @@ import { AIService } from '../../services'; @Component({ selector: 'app-translate-exercises-llm-dlg', templateUrl: './translate-exercises-llm-dialog.html', + changeDetection: ChangeDetectionStrategy.Eager, imports: [ MatFormFieldModule, MatInputModule, diff --git a/src/app/pages/translate-exercises/translate-exercises-options-dialog.component.ts b/src/app/pages/translate-exercises/translate-exercises-options-dialog.component.ts index a7dc2f4..5cf36a2 100644 --- a/src/app/pages/translate-exercises/translate-exercises-options-dialog.component.ts +++ b/src/app/pages/translate-exercises/translate-exercises-options-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, inject, model } from '@angular/core'; +import { Component, Inject, inject, model, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; @@ -20,6 +20,7 @@ import { TranslateDirectionEnum } from '../../interfaces'; @Component({ selector: 'app-translate-exercises-options-dlg', templateUrl: './translate-exercises-options-dialog.html', + changeDetection: ChangeDetectionStrategy.Eager, imports: [ MatFormFieldModule, MatInputModule, @@ -49,7 +50,9 @@ export class TranslateExercisesOptionsDialogComponent { ? this.data.reciteQueuesCount : (this.data.currentSettings?.countOfItems ?? 20) ); - readonly direction = model(this.data.currentSettings?.direction ?? TranslateDirectionEnum.EnglishToChinese); + readonly direction = model( + this.data.currentSettings?.direction ?? TranslateDirectionEnum.EnglishToChinese + ); constructor( @Inject(MAT_DIALOG_DATA) diff --git a/src/app/pages/translate-exercises/translate-exercises-printoptions-dialog.component.ts b/src/app/pages/translate-exercises/translate-exercises-printoptions-dialog.component.ts index 6c880dd..72f921a 100644 --- a/src/app/pages/translate-exercises/translate-exercises-printoptions-dialog.component.ts +++ b/src/app/pages/translate-exercises/translate-exercises-printoptions-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, inject, model } from '@angular/core'; +import { Component, Inject, inject, model, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; @@ -42,6 +42,7 @@ import { MatDateFnsModule, provideDateFnsAdapter } from '@angular/material-date- MatDateFnsModule, TranslocoModule, ], + changeDetection: ChangeDetectionStrategy.Eager, providers: [ provideDateFnsAdapter(), { provide: MAT_DATE_FORMATS, useValue: MY_DATE_FORMATS }, diff --git a/src/app/pages/vocabulary-exercises/vocabulary-exercises-word-list.component.spec.ts b/src/app/pages/vocabulary-exercises/vocabulary-exercises-word-list.component.spec.ts index 5cf23b1..15c853e 100644 --- a/src/app/pages/vocabulary-exercises/vocabulary-exercises-word-list.component.spec.ts +++ b/src/app/pages/vocabulary-exercises/vocabulary-exercises-word-list.component.spec.ts @@ -1,11 +1,16 @@ import { SelectionModel } from '@angular/cdk/collections'; -import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClient, withXhr } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; -import type { ComponentFixture} from '@angular/core/testing'; +import type { ComponentFixture } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing'; import { MatTableDataSource } from '@angular/material/table'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { TranslocoModule, TranslocoService, TRANSLOCO_TRANSPILER, TRANSLOCO_MISSING_HANDLER } from '@jsverse/transloco'; +import { + TranslocoModule, + TranslocoService, + TRANSLOCO_TRANSPILER, + TRANSLOCO_MISSING_HANDLER, +} from '@jsverse/transloco'; import { FilterJoinType, FilterOperation } from 'actslib'; import type { IFilterCondition, IFilterDefinition } from 'actslib'; import { of } from 'rxjs'; @@ -16,7 +21,11 @@ import type { LearnEnglishWordFileItem, LearningContent } from '../../interfaces import { VocabularyExercisesWordListComponent } from './vocabulary-exercises-word-list.component'; // cond/andG/orG build the actslib definition the filter input now carries. -const cond = (property: string, operation: FilterOperation, lowValue: string | number): IFilterCondition => ({ +const cond = ( + property: string, + operation: FilterOperation, + lowValue: string | number +): IFilterCondition => ({ property, operation, lowValue, @@ -58,7 +67,7 @@ describe('VocabularyExercisesWordListComponent', () => { { provide: TranslocoService, useValue: mockTransloco() }, { provide: TRANSLOCO_TRANSPILER, useValue: {} }, { provide: TRANSLOCO_MISSING_HANDLER, useValue: {} }, - provideHttpClient(), + provideHttpClient(withXhr()), provideHttpClientTesting(), ], }).compileComponents(); @@ -68,8 +77,14 @@ describe('VocabularyExercisesWordListComponent', () => { fixture.componentRef.setInput('allFiles', [] as LearningContent[]); fixture.componentRef.setInput('selectedFile', undefined); fixture.componentRef.setInput('isLoadingContents', false); - fixture.componentRef.setInput('dataSource', new MatTableDataSource([])); - fixture.componentRef.setInput('selection', new SelectionModel(true, [])); + fixture.componentRef.setInput( + 'dataSource', + new MatTableDataSource([]) + ); + fixture.componentRef.setInput( + 'selection', + new SelectionModel(true, []) + ); fixture.componentRef.setInput('contentRatings', new Map()); fixture.componentRef.setInput('filterDefinition', andG()); // Do NOT call detectChanges: class-logic tests only (getters read signal @@ -82,13 +97,19 @@ describe('VocabularyExercisesWordListComponent', () => { }); it('builds a readable Filter menu summary when conditions are active', () => { - fixture.componentRef.setInput('filterDefinition', andG(cond('enword', FilterOperation.BeginsWith, 'a'))); + fixture.componentRef.setInput( + 'filterDefinition', + andG(cond('enword', FilterOperation.BeginsWith, 'a')) + ); expect(component.hasFilter).toBe(true); expect(component.filterMenuLabel).toBe('word opStartsWith a'); }); it('labels cnword conditions with the Chinese field name', () => { - fixture.componentRef.setInput('filterDefinition', andG(cond('cnword', FilterOperation.Contains, '苹果'))); + fixture.componentRef.setInput( + 'filterDefinition', + andG(cond('cnword', FilterOperation.Contains, '苹果')) + ); expect(component.filterMenuLabel).toBe('chinese opContains 苹果'); }); @@ -99,10 +120,13 @@ describe('VocabularyExercisesWordListComponent', () => { }); it('joins word and rating leaves with the group join word in one summary', () => { - fixture.componentRef.setInput('filterDefinition', andG( - cond('enword', FilterOperation.BeginsWith, 'a'), - cond('rating', FilterOperation.GreaterOrEqual, 3), - )); + fixture.componentRef.setInput( + 'filterDefinition', + andG( + cond('enword', FilterOperation.BeginsWith, 'a'), + cond('rating', FilterOperation.GreaterOrEqual, 3) + ) + ); // Numeric properties render comparison operators as symbols; the join word // is translated (mock returns the key's last segment). expect(component.hasFilter).toBe(true); @@ -110,25 +134,28 @@ describe('VocabularyExercisesWordListComponent', () => { }); it('shows the OR join word for an OR-joined group', () => { - fixture.componentRef.setInput('filterDefinition', orG( - cond('enword', FilterOperation.BeginsWith, 'a'), - cond('rating', FilterOperation.Equal, 5), - )); + fixture.componentRef.setInput( + 'filterDefinition', + orG(cond('enword', FilterOperation.BeginsWith, 'a'), cond('rating', FilterOperation.Equal, 5)) + ); expect(component.filterMenuLabel).toBe('word opStartsWith a joinOr rating = 5'); }); it('wraps nested multi-member groups in parentheses in the menu summary', () => { - fixture.componentRef.setInput('filterDefinition', orG( - andG( - cond('enword', FilterOperation.Equal, 'a'), - cond('enword', FilterOperation.Equal, 'b'), - ), - )); + fixture.componentRef.setInput( + 'filterDefinition', + orG( + andG(cond('enword', FilterOperation.Equal, 'a'), cond('enword', FilterOperation.Equal, 'b')) + ) + ); expect(component.filterMenuLabel).toBe('(word opEqual a joinAnd word opEqual b)'); }); it('shows a rating-only summary when no vocabulary condition is active', () => { - fixture.componentRef.setInput('filterDefinition', andG(cond('rating', FilterOperation.GreaterOrEqual, 3))); + fixture.componentRef.setInput( + 'filterDefinition', + andG(cond('rating', FilterOperation.GreaterOrEqual, 3)) + ); expect(component.hasFilter).toBe(true); expect(component.filterMenuLabel).toBe('rating >= 3'); }); @@ -218,9 +245,7 @@ describe('VocabularyExercisesWordListComponent', () => { }); describe('rating column visibility for temp content (L2)', () => { - const rows: LearnEnglishWordFileItem[] = [ - { id: 1, enword: 'hello', cnword: '你好' }, - ]; + const rows: LearnEnglishWordFileItem[] = [{ id: 1, enword: 'hello', cnword: '你好' }]; function setupRatingsEnabled(enabled: boolean): void { const dataSource = new MatTableDataSource(rows.slice()); diff --git a/src/app/services/ai.service.spec.ts b/src/app/services/ai.service.spec.ts index 7c42660..fff07ac 100644 --- a/src/app/services/ai.service.spec.ts +++ b/src/app/services/ai.service.spec.ts @@ -1,4 +1,4 @@ -import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { provideHttpClient, withInterceptorsFromDi, withXhr } from '@angular/common/http'; import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { firstValueFrom } from 'rxjs'; @@ -21,7 +21,7 @@ describe('AIService', () => { imports: [], providers: [ AIService, - provideHttpClient(withInterceptorsFromDi()), + provideHttpClient(withXhr(), withInterceptorsFromDi()), provideHttpClientTesting(), { provide: UserCodeService, useValue: userCodeSpy }, ], diff --git a/src/app/services/audio-service.service.extended.spec.ts b/src/app/services/audio-service.service.extended.spec.ts index a53fdac..df042d4 100644 --- a/src/app/services/audio-service.service.extended.spec.ts +++ b/src/app/services/audio-service.service.extended.spec.ts @@ -1,4 +1,4 @@ -import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClient, withXhr } from '@angular/common/http'; import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing'; import { NgZone } from '@angular/core'; import { TestBed } from '@angular/core/testing'; @@ -82,7 +82,7 @@ describe('AudioService Extended Tests', () => { { provide: NgZone, useValue: new NgZone({ enableLongStackTrace: false }) }, { provide: HOWL_FACTORY, useValue: mockHowlFactory }, { provide: HOWLER_GLOBAL, useValue: mockHowlerGlobal }, - provideHttpClient(), + provideHttpClient(withXhr()), provideHttpClientTesting(), ], }); diff --git a/src/app/services/audio-service.service.spec.ts b/src/app/services/audio-service.service.spec.ts index b558522..2c12242 100644 --- a/src/app/services/audio-service.service.spec.ts +++ b/src/app/services/audio-service.service.spec.ts @@ -1,4 +1,4 @@ -import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClient, withXhr } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { NgZone } from '@angular/core'; import { TestBed } from '@angular/core/testing'; @@ -127,7 +127,7 @@ describe('AudioService', () => { providers: [ AudioService, { provide: NgZone, useValue: new NgZone({}) }, - provideHttpClient(), + provideHttpClient(withXhr()), provideHttpClientTesting(), ], }); @@ -239,7 +239,8 @@ describe('AudioService', () => { beforeEach(() => { originalSpeechSynthesis = window.speechSynthesis; - originalCtor = (window as unknown as { SpeechSynthesisUtterance?: unknown }).SpeechSynthesisUtterance; + originalCtor = (window as unknown as { SpeechSynthesisUtterance?: unknown }) + .SpeechSynthesisUtterance; voicesChangedHandler = null; getVoicesMock = vi.fn(() => []); speakMock = vi.fn(); @@ -283,7 +284,8 @@ describe('AudioService', () => { configurable: true, value: originalSpeechSynthesis, }); - (window as unknown as { SpeechSynthesisUtterance: unknown }).SpeechSynthesisUtterance = originalCtor; + (window as unknown as { SpeechSynthesisUtterance: unknown }).SpeechSynthesisUtterance = + originalCtor; }); it('should not throw and should not speak when speechSynthesis is unavailable', () => { @@ -346,7 +348,7 @@ describe('AudioService', () => { const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); service.speakWord('hello'); const utterance = speakMock.mock.calls[0][0] as SpeechSynthesisUtterance; - const makeEvent = (error: string) => ({ error } as unknown as SpeechSynthesisErrorEvent); + const makeEvent = (error: string) => ({ error }) as unknown as SpeechSynthesisErrorEvent; utterance.onerror!(makeEvent('interrupted')); utterance.onerror!(makeEvent('canceled')); expect(warnSpy).not.toHaveBeenCalled(); diff --git a/src/app/services/learning-content.service.spec.ts b/src/app/services/learning-content.service.spec.ts index 43d26e2..d5e9a0a 100644 --- a/src/app/services/learning-content.service.spec.ts +++ b/src/app/services/learning-content.service.spec.ts @@ -1,4 +1,4 @@ -import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { provideHttpClient, withInterceptorsFromDi, withXhr } from '@angular/common/http'; import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; @@ -109,7 +109,7 @@ describe('LearningContentService', () => { TestBed.configureTestingModule({ providers: [ LearningContentService, - provideHttpClient(withInterceptorsFromDi()), + provideHttpClient(withXhr(), withInterceptorsFromDi()), provideHttpClientTesting(), ], }); diff --git a/src/app/services/learning-rating.service.spec.ts b/src/app/services/learning-rating.service.spec.ts index e6d8212..6aa7d2f 100644 --- a/src/app/services/learning-rating.service.spec.ts +++ b/src/app/services/learning-rating.service.spec.ts @@ -1,4 +1,4 @@ -import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { provideHttpClient, withInterceptorsFromDi, withXhr } from '@angular/common/http'; import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; @@ -23,7 +23,7 @@ describe('LearningRatingService', () => { TestBed.configureTestingModule({ providers: [ LearningRatingService, - provideHttpClient(withInterceptorsFromDi()), + provideHttpClient(withXhr(), withInterceptorsFromDi()), provideHttpClientTesting(), ], }); diff --git a/src/app/shared/filter-dialog/filter-dialog.component.spec.ts b/src/app/shared/filter-dialog/filter-dialog.component.spec.ts index b735bd6..e64fcb2 100644 --- a/src/app/shared/filter-dialog/filter-dialog.component.spec.ts +++ b/src/app/shared/filter-dialog/filter-dialog.component.spec.ts @@ -1,4 +1,4 @@ -import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClient, withXhr } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import type { ComponentFixture } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing'; @@ -14,7 +14,6 @@ import type { EnumLike, IFilterCondition, IFilterDefinition } from 'actslib'; import { of } from 'rxjs'; import { vi } from 'vitest'; - import type { FilterDialogData, FilterableProperty } from './filter-dialog-model'; import { SharedFilterDialogComponent } from './filter-dialog.component'; @@ -38,19 +37,34 @@ const COLOR_VALUES: EnumLike = { red: 'red', blue: 'blue' }; const IS_PHRASE = { id: 'isPhrase', labelKey: 'test.opIsPhrase', - emit: (property: string): IFilterCondition => ({ property, operation: FilterOperation.Contains, lowValue: ' ' }), - recognize: (c: IFilterCondition): boolean => c.operation === FilterOperation.Contains && c.lowValue === ' ', + emit: (property: string): IFilterCondition => ({ + property, + operation: FilterOperation.Contains, + lowValue: ' ', + }), + recognize: (c: IFilterCondition): boolean => + c.operation === FilterOperation.Contains && c.lowValue === ' ', }; const SCHEMA: FilterableProperty[] = [ - { key: 'enword', labelKey: 'test.word', kind: 'string', customOperators: [IS_PHRASE], - prepareValue: v => String(v).trim().toLowerCase() }, + { + key: 'enword', + labelKey: 'test.word', + kind: 'string', + customOperators: [IS_PHRASE], + prepareValue: v => String(v).trim().toLowerCase(), + }, { key: 'rating', labelKey: 'test.rating', kind: 'number' }, - { key: 'color', labelKey: 'test.color', kind: 'enum', enumValues: COLOR_VALUES, + { + key: 'color', + labelKey: 'test.color', + kind: 'enum', + enumValues: COLOR_VALUES, choices: [ { value: 'red', labelKey: 'test.red' }, { value: 'blue', labelKey: 'test.blue' }, - ] }, + ], + }, ]; describe('SharedFilterDialogComponent', () => { @@ -67,7 +81,7 @@ describe('SharedFilterDialogComponent', () => { { provide: TranslocoService, useValue: mockTranslocoService() }, { provide: TRANSLOCO_TRANSPILER, useValue: {} }, { provide: TRANSLOCO_MISSING_HANDLER, useValue: {} }, - provideHttpClient(), + provideHttpClient(withXhr()), provideHttpClientTesting(), ], }).compileComponents(); @@ -78,9 +92,13 @@ describe('SharedFilterDialogComponent', () => { const rootNode = () => component().root(); const rows = () => fixture.nativeElement.querySelectorAll('.fdlg-node-row'); const toolbarButtons = () => - fixture.nativeElement.querySelectorAll('.fdlg-tree-toolbar button') as NodeListOf; + fixture.nativeElement.querySelectorAll( + '.fdlg-tree-toolbar button' + ) as NodeListOf; const actionButtons = () => - fixture.nativeElement.querySelectorAll('mat-dialog-actions button') as NodeListOf; + fixture.nativeElement.querySelectorAll( + 'mat-dialog-actions button' + ) as NodeListOf; const seedDef: IFilterDefinition = { join: FilterJoinType.AND, @@ -102,8 +120,8 @@ describe('SharedFilterDialogComponent', () => { expect(select, `select ${cssClass}`).toBeTruthy(); select.click(); fixture.detectChanges(); - const option = Array.from(document.querySelectorAll('mat-option')).find( - o => o.textContent?.includes(optionText) + const option = Array.from(document.querySelectorAll('mat-option')).find(o => + o.textContent?.includes(optionText) ); expect(option, `mat-option containing ${optionText}`).toBeDefined(); option?.click(); @@ -214,7 +232,12 @@ describe('SharedFilterDialogComponent', () => { root: { join: FilterJoinType.AND, conditions: [ - { property: 'color', operation: FilterOperation.Equal, lowValue: 'red', enumValues: COLOR_VALUES }, + { + property: 'color', + operation: FilterOperation.Equal, + lowValue: 'red', + enumValues: COLOR_VALUES, + }, { property: 'rating', operation: FilterOperation.Equal, lowValue: 4 }, ], }, @@ -224,9 +247,11 @@ describe('SharedFilterDialogComponent', () => { firstRow[1].click(); // the enum leaf fixture.detectChanges(); - const checkboxes: NodeListOf = fixture.nativeElement.querySelectorAll('.fdlg-choices input[type=checkbox]'); + const checkboxes: NodeListOf = fixture.nativeElement.querySelectorAll( + '.fdlg-choices input[type=checkbox]' + ); expect(checkboxes.length).toBe(2); - expect((checkboxes[0]).checked).toBe(true); // 'red' seeded + expect(checkboxes[0].checked).toBe(true); // 'red' seeded // Check 'blue' too — the leaf now holds a two-value selection. checkboxes[1].click(); @@ -238,7 +263,10 @@ describe('SharedFilterDialogComponent', () => { const arg = closeSpy.mock.calls[0][0] as { root: IFilterDefinition }; const group = arg.root.conditions[0] as IFilterDefinition; expect(group.join).toBe(FilterJoinType.OR); - expect((group.conditions as IFilterCondition[]).map(c => c.lowValue)).toEqual(['red', 'blue']); + expect((group.conditions as IFilterCondition[]).map(c => c.lowValue)).toEqual([ + 'red', + 'blue', + ]); expect((group.conditions as IFilterCondition[])[0].enumValues).toBe(COLOR_VALUES); }); @@ -286,13 +314,18 @@ describe('SharedFilterDialogComponent', () => { const input: HTMLInputElement = fixture.nativeElement.querySelector('.fdlg-text input'); typeInto(input, ' '); expect(submit.disabled).toBe(true); - expect(fixture.nativeElement.querySelector('.fdlg-error').textContent).toContain('common.filterNeedsValue'); + expect(fixture.nativeElement.querySelector('.fdlg-error').textContent).toContain( + 'common.filterNeedsValue' + ); }); it('an empty nested group is flagged and blocks submit; the root is exempt', async () => { await createWith({ properties: SCHEMA, - root: { join: FilterJoinType.AND, conditions: [{ property: 'rating', operation: FilterOperation.Equal, lowValue: 2 }] }, + root: { + join: FilterJoinType.AND, + conditions: [{ property: 'rating', operation: FilterOperation.Equal, lowValue: 2 }], + }, }); fixture.detectChanges(); diff --git a/src/app/shared/footer/footer.ts b/src/app/shared/footer/footer.ts index a02b14f..c2b1c5c 100644 --- a/src/app/shared/footer/footer.ts +++ b/src/app/shared/footer/footer.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; import { TranslocoModule } from '@jsverse/transloco'; import { environment } from '../../../environments/environment'; @@ -8,6 +8,7 @@ import { AppLogoComponent } from '../logo/logo'; selector: 'app-footer', templateUrl: './footer.html', styleUrls: ['./footer.scss'], + changeDetection: ChangeDetectionStrategy.Eager, imports: [AppLogoComponent, TranslocoModule], }) export class FooterComponent { diff --git a/src/app/shared/knowledge-exercise-form/knowledge-exercise-form.component.ts b/src/app/shared/knowledge-exercise-form/knowledge-exercise-form.component.ts index 8eb4a67..abb073c 100644 --- a/src/app/shared/knowledge-exercise-form/knowledge-exercise-form.component.ts +++ b/src/app/shared/knowledge-exercise-form/knowledge-exercise-form.component.ts @@ -1,11 +1,14 @@ -import { Component, Input, input } from '@angular/core'; +import { Component, Input, input, ChangeDetectionStrategy } from '@angular/core'; import { FormGroup, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatFormFieldModule } from '@angular/material/form-field'; import { NgxPrintModule } from 'ngx-print'; import type { QuestionBankTypeKeys } from '../../interfaces'; -import type { KnowledgeExercisePrintOption, QuestionBankItemBase } from '../../interfaces/questionbank-base'; +import type { + KnowledgeExercisePrintOption, + QuestionBankItemBase, +} from '../../interfaces/questionbank-base'; import { KnowledgeExerciseItemComponent } from '../knowledge-exercise-item/knowledge-exercise-item.component'; @Component({ @@ -16,9 +19,10 @@ import { KnowledgeExerciseItemComponent } from '../knowledge-exercise-item/knowl KnowledgeExerciseItemComponent, MatFormFieldModule, MatButtonModule, - NgxPrintModule + NgxPrintModule, ], templateUrl: './knowledge-exercise-form.component.html', + changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './knowledge-exercise-form.component.scss', }) export class KnowledgeExerciseFormComponent { @@ -54,10 +58,10 @@ export class KnowledgeExerciseFormComponent { const totalgrp = {}; if (this._questions && this._questions.length > 0) { - this._questions.forEach((qtn) => { + this._questions.forEach(qtn => { const grp = qtn.getFormControls(); Object.assign(totalgrp, grp); - }) + }); } this.form = new FormGroup(totalgrp); } diff --git a/src/app/shared/knowledge-exercise-item/knowledge-exercise-item.component.ts b/src/app/shared/knowledge-exercise-item/knowledge-exercise-item.component.ts index 437c596..45f4baa 100644 --- a/src/app/shared/knowledge-exercise-item/knowledge-exercise-item.component.ts +++ b/src/app/shared/knowledge-exercise-item/knowledge-exercise-item.component.ts @@ -1,6 +1,6 @@ -import type { AfterViewInit, ElementRef} from '@angular/core'; -import { Component, Input, input, ViewChild } from '@angular/core'; -import type { FormGroup} from '@angular/forms'; +import type { AfterViewInit, ElementRef } from '@angular/core'; +import { Component, Input, input, ViewChild, ChangeDetectionStrategy } from '@angular/core'; +import type { FormGroup } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatFormFieldModule } from '@angular/material/form-field'; @@ -10,21 +10,30 @@ import { MatSelectModule } from '@angular/material/select'; import { TranslocoModule } from '@jsverse/transloco'; import type { QuestionBankTypeKeys, ValidOptionKeys } from '../../interfaces'; -import { getQuestionBankLevelName, QuestionBankTypeEnum, VALID_OPTION_KEYS } from '../../interfaces'; +import { + getQuestionBankLevelName, + QuestionBankTypeEnum, + VALID_OPTION_KEYS, +} from '../../interfaces'; import type { - QuestionBankItemBase, QuestionBankItemCloze, QuestionBankItemDictation, QuestionBankItemEssay, QuestionBankItemFillInTheBlank, - QuestionBankItemFillInTheBlankParagraph, QuestionBankItemListeningComprehension, QuestionBankItemMultipleChoice, QuestionBankItemReadingComprehension, - QuestionBankItemShortAnswer, QuestionBankItemSingleChoice, - QuestionBankItemTrueFalse + QuestionBankItemBase, + QuestionBankItemCloze, + QuestionBankItemDictation, + QuestionBankItemEssay, + QuestionBankItemFillInTheBlank, + QuestionBankItemFillInTheBlankParagraph, + QuestionBankItemListeningComprehension, + QuestionBankItemMultipleChoice, + QuestionBankItemReadingComprehension, + QuestionBankItemShortAnswer, + QuestionBankItemSingleChoice, + QuestionBankItemTrueFalse, } from '../../interfaces/questionbank-base'; import 'katex/contrib/auto-render'; // 为TypeScript声明renderMathInElement函数 -declare function renderMathInElement( - element: HTMLElement, - options?: any -): void; +declare function renderMathInElement(element: HTMLElement, options?: any): void; @Component({ selector: 'app-knowledge-exercise-item', @@ -39,6 +48,7 @@ declare function renderMathInElement( TranslocoModule, ], templateUrl: './knowledge-exercise-item.component.html', + changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './knowledge-exercise-item.component.scss', }) export class KnowledgeExerciseItemComponent implements AfterViewInit { @@ -100,8 +110,9 @@ export class KnowledgeExerciseItemComponent implements AfterViewInit { const scitem = this._question as QuestionBankItemSingleChoice; this.questionString = scitem.question; this.refToID = scitem.referToID; - (Object.keys(VALID_OPTION_KEYS) as ValidOptionKeys[]).forEach((key) => { - if (scitem.options && scitem.options[key] !== undefined) { // Check option exists + (Object.keys(VALID_OPTION_KEYS) as ValidOptionKeys[]).forEach(key => { + if (scitem.options && scitem.options[key] !== undefined) { + // Check option exists this.options.push({ key: key.toString(), value: scitem.options[key] }); } }); @@ -112,8 +123,9 @@ export class KnowledgeExerciseItemComponent implements AfterViewInit { const scitem = this._question as QuestionBankItemMultipleChoice; this.refToID = scitem.referToID; this.questionString = scitem.question; - (Object.keys(VALID_OPTION_KEYS) as ValidOptionKeys[]).forEach((key) => { - if (scitem.options && scitem.options[key] !== undefined) { // Check option exists + (Object.keys(VALID_OPTION_KEYS) as ValidOptionKeys[]).forEach(key => { + if (scitem.options && scitem.options[key] !== undefined) { + // Check option exists this.options.push({ key: key.toString(), value: scitem.options[key] }); } }); @@ -126,7 +138,7 @@ export class KnowledgeExerciseItemComponent implements AfterViewInit { this.paragraphes = []; this.paragraphes = dictitem.paragraphes; this._questionLevelName = getQuestionBankLevelName(dictitem.questionLevel); - this._dictationCountOfInputs = dictitem.countOfInputs; + this._dictationCountOfInputs = dictitem.countOfInputs; break; } @@ -140,27 +152,27 @@ export class KnowledgeExerciseItemComponent implements AfterViewInit { case QuestionBankTypeEnum.ReadingComprehension: { const scitem = this._question as QuestionBankItemReadingComprehension; - this.questionLines = scitem.question.split('\n'); + this.questionLines = scitem.question.split('\n'); break; } case QuestionBankTypeEnum.Cloze: { const scitem = this._question as QuestionBankItemCloze; this.questionLines = scitem.question.split('\n'); - break; + break; } case QuestionBankTypeEnum.ListeningComprehension: { const scitem = this._question as QuestionBankItemListeningComprehension; this.questionLines = scitem.question.split('\n'); - break; + break; } case QuestionBankTypeEnum.TrueFalse: { const scitem = this._question as QuestionBankItemTrueFalse; this.questionString = scitem.question; this.refToID = scitem.referToID; - break; + break; } case QuestionBankTypeEnum.Essay: { @@ -172,17 +184,21 @@ export class KnowledgeExerciseItemComponent implements AfterViewInit { } default: { - break; + break; } } } - get question(): QuestionBankItemBase | undefined { return this._question; } + get question(): QuestionBankItemBase | undefined { + return this._question; + } @Input() hideLabels: QuestionBankTypeKeys[] = []; @Input() isSubItem: boolean = false; isLabelOfQuestionTypeHidden(): boolean { - return !(this.hideLabels && this.hideLabels.includes(this._question?.itemType as QuestionBankTypeKeys)); + return !( + this.hideLabels && this.hideLabels.includes(this._question?.itemType as QuestionBankTypeKeys) + ); } getFormControlName(optkey: any) { @@ -202,9 +218,9 @@ export class KnowledgeExerciseItemComponent implements AfterViewInit { { left: '$$', right: '$$', display: true }, { left: '$', right: '$', display: false }, { left: '\\(', right: '\\)', display: false }, - { left: '\\[', right: '\\]', display: true } + { left: '\\[', right: '\\]', display: true }, ], - throwOnError: false + throwOnError: false, }); } } diff --git a/src/app/shared/language-selector/language-selector.component.ts b/src/app/shared/language-selector/language-selector.component.ts index 1c59ae1..b9cf16a 100644 --- a/src/app/shared/language-selector/language-selector.component.ts +++ b/src/app/shared/language-selector/language-selector.component.ts @@ -1,7 +1,7 @@ import { LiveAnnouncer } from '@angular/cdk/a11y'; import { CommonModule } from '@angular/common'; import type { OnInit } from '@angular/core'; -import { Component, inject, ViewEncapsulation } from '@angular/core'; +import { Component, inject, ViewEncapsulation, ChangeDetectionStrategy } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; @@ -44,6 +44,7 @@ import { TranslocoModule, TranslocoService } from '@jsverse/transloco'; `, styleUrl: './language-selector.component.scss', + changeDetection: ChangeDetectionStrategy.Eager, encapsulation: ViewEncapsulation.None, }) export class LanguageSelectorComponent implements OnInit { diff --git a/src/app/shared/logo/logo.ts b/src/app/shared/logo/logo.ts index a5e3935..47a1032 100644 --- a/src/app/shared/logo/logo.ts +++ b/src/app/shared/logo/logo.ts @@ -1,9 +1,10 @@ -import { Component, HostBinding } from '@angular/core'; +import { Component, HostBinding, ChangeDetectionStrategy } from '@angular/core'; @Component({ selector: 'app-logo', styleUrl: 'logo.scss', templateUrl: './logo.html', + changeDetection: ChangeDetectionStrategy.Eager, standalone: true, }) export class AppLogoComponent { diff --git a/src/app/shared/markdown-content/markdown-content.component.spec.ts b/src/app/shared/markdown-content/markdown-content.component.spec.ts index ccb69d3..fae65c7 100644 --- a/src/app/shared/markdown-content/markdown-content.component.spec.ts +++ b/src/app/shared/markdown-content/markdown-content.component.spec.ts @@ -1,4 +1,4 @@ -import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClient, withXhr } from '@angular/common/http'; import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing'; import { SimpleChange } from '@angular/core'; import type { ComponentFixture } from '@angular/core/testing'; @@ -14,7 +14,7 @@ describe('MarkdownContentComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [MarkdownContentComponent], - providers: [provideHttpClient(), provideHttpClientTesting()], + providers: [provideHttpClient(withXhr()), provideHttpClientTesting()], }).compileComponents(); fixture = TestBed.createComponent(MarkdownContentComponent); diff --git a/src/app/shared/mathitem/mathitem.ts b/src/app/shared/mathitem/mathitem.ts index 1151484..45e9309 100644 --- a/src/app/shared/mathitem/mathitem.ts +++ b/src/app/shared/mathitem/mathitem.ts @@ -1,5 +1,12 @@ import type { AfterViewInit, ElementRef, OnInit } from '@angular/core'; -import { Component, Input, ViewChild, ViewEncapsulation, inject } from '@angular/core'; +import { + Component, + Input, + ViewChild, + ViewEncapsulation, + inject, + ChangeDetectionStrategy, +} from '@angular/core'; import { replaceAtSymbols } from '../../interfaces'; import { KatexService } from '../../services/katex.service'; @@ -9,6 +16,7 @@ import { KatexService } from '../../services/katex.service'; templateUrl: 'mathitem.html', styleUrls: ['mathitem.scss'], encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.Eager, imports: [], }) export class MathItemComponent implements OnInit, AfterViewInit { diff --git a/src/app/shared/navigation-focus/navigation-focus.spec.ts b/src/app/shared/navigation-focus/navigation-focus.spec.ts index 4544ec5..4e1d2d0 100644 --- a/src/app/shared/navigation-focus/navigation-focus.spec.ts +++ b/src/app/shared/navigation-focus/navigation-focus.spec.ts @@ -1,5 +1,5 @@ import { provideLocationMocks } from '@angular/common/testing'; -import { Component, NgZone } from '@angular/core'; +import { Component, NgZone, ChangeDetectionStrategy } from '@angular/core'; import type { ComponentFixture } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; @@ -136,6 +136,7 @@ describe('Navigation focus service', () => { `, standalone: true, + changeDetection: ChangeDetectionStrategy.Eager, imports: [NavigationFocusDirective], }) class NavigationFocusTestComponent {} @@ -143,6 +144,7 @@ class NavigationFocusTestComponent {} @Component({ selector: 'app-route-test', template: '', + changeDetection: ChangeDetectionStrategy.Eager, standalone: true, }) class RouteTestComponent {} diff --git a/src/app/shared/style-manager/style-manager.spec.ts b/src/app/shared/style-manager/style-manager.spec.ts index 1da3f85..8892686 100644 --- a/src/app/shared/style-manager/style-manager.spec.ts +++ b/src/app/shared/style-manager/style-manager.spec.ts @@ -1,20 +1,17 @@ -import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClient, withXhr } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { StyleManager } from './style-manager'; - describe('StyleManager', () => { let styleManager: StyleManager; - beforeEach(() => TestBed.configureTestingModule({ - providers: [ - StyleManager, - provideHttpClient(), - provideHttpClientTesting(), - ] - })); + beforeEach(() => + TestBed.configureTestingModule({ + providers: [StyleManager, provideHttpClient(withXhr()), provideHttpClientTesting()], + }) + ); beforeEach(() => { styleManager = TestBed.inject(StyleManager); diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index bc9081f..02792c4 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -2,7 +2,7 @@ export const environment = { homeurl: 'https://www.alvachien.com/learning', production: true, releasedate: '2026-08-30', - version: '1.9.0', + version: '1.9.1', apiUrl: 'https://www.alvachien.com/learningutil', pageTitle: 'Knowledge Builder', loginRequired: true, diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 0776f7c..a3f7cbc 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -7,7 +7,7 @@ export const environment = { homeurl: 'http://localhost:29800', production: false, releasedate: '2026-08-30', - version: '1.9.0', + version: '1.9.1', apiUrl: 'https://localhost:7135', pageTitle: 'Knowledge Builder', loginRequired: true, diff --git a/tsconfig.app.json b/tsconfig.app.json index 3775b37..2d80af6 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -11,5 +11,13 @@ ], "include": [ "src/**/*.d.ts" - ] + ], + "angularCompilerOptions": { + "extendedDiagnostics": { + "checks": { + "nullishCoalescingNotNullable": "suppress", + "optionalChainNotNullable": "suppress" + } + } + } } diff --git a/tsconfig.spec.json b/tsconfig.spec.json index ab65e2e..8d510c7 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -5,5 +5,16 @@ "types": ["vitest/globals", "node"] }, "files": ["src/test-setup.ts"], - "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ], + "angularCompilerOptions": { + "extendedDiagnostics": { + "checks": { + "nullishCoalescingNotNullable": "suppress", + "optionalChainNotNullable": "suppress" + } + } + } } From 391ed07cedbf1edc5b08fd21d3e142d4fd10b8fb Mon Sep 17 00:00:00 2001 From: Alva Chien Date: Sat, 5 Sep 2026 13:11:42 +0800 Subject: [PATCH 2/4] feat(filter-dialog): shared launcher, tree-editor refinements, 800px dialog - Extract openFilterDialog() + FILTER_DIALOG_CONFIG into one shared launcher and migrate all four list pages (vocabulary, knowledge, Chinese, translate) - Rework the model/editor around the hierarchy contract: single-node root, selection-driven three-button toolbar, splitter-sized panes; dialog width now 800px (percentage split keeps the pane ratio) - docs: refresh design + vocabulary architecture notes; add filter-hierarchy-contract.md and filter-dialog-review.md - actslib 0.6.81 -> 0.6.83; i18n keys for tree/splitter actions - bump version to 1.9.5 (releasedate 2026-09-05) Co-Authored-By: Claude Code --- CLAUDE.md | 4 +- docs/filter-dialog-review.md | 310 +++++++++++++++++ docs/filter-hierarchy-contract.md | 213 ++++++++++++ docs/reusable-filter-dialog-design.md | 98 ++++-- docs/vocabulary-exercises-architecture.md | 18 +- package-lock.json | 8 +- package.json | 4 +- src/app/interfaces/knowledge-list-filter.ts | 17 +- src/app/interfaces/learnchinese.ts | 19 +- src/app/interfaces/sentence.ts | 19 +- src/app/interfaces/vocabulary.spec.ts | 5 +- src/app/interfaces/vocabulary.ts | 19 +- .../chinese-exercises-list.component.ts | 4 +- .../chinese-exercises.component.ts | 47 +-- .../knowledge-exercises-list.component.ts | 4 +- .../knowledge-exercises.component.ts | 47 +-- ...slate-exercises-sentence-list.component.ts | 4 +- .../translate-exercises.component.ts | 47 +-- ...ocabulary-exercises-word-list.component.ts | 4 +- .../vocabulary-exercises.component.spec.ts | 17 +- .../vocabulary-exercises.component.ts | 44 +-- .../filter-dialog-launcher.spec.ts | 88 +++++ .../filter-dialog/filter-dialog-launcher.ts | 69 ++++ .../filter-dialog/filter-dialog-model.spec.ts | 145 +++++++- .../filter-dialog/filter-dialog-model.ts | 161 ++++++--- .../filter-dialog.component.html | 13 +- .../filter-dialog.component.spec.ts | 329 +++++++++++++++--- .../filter-dialog/filter-dialog.component.ts | 152 ++++++-- src/app/shared/filter-dialog/index.ts | 1 + src/assets/data/i18n/en.json | 16 +- src/assets/data/i18n/zh-CN.json | 16 +- src/environments/environment.prod.ts | 4 +- src/environments/environment.ts | 4 +- 33 files changed, 1554 insertions(+), 396 deletions(-) create mode 100644 docs/filter-dialog-review.md create mode 100644 docs/filter-hierarchy-contract.md create mode 100644 src/app/shared/filter-dialog/filter-dialog-launcher.spec.ts create mode 100644 src/app/shared/filter-dialog/filter-dialog-launcher.ts diff --git a/CLAUDE.md b/CLAUDE.md index b4d7a86..edb4657 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -87,8 +87,8 @@ Decomposed into a container component, presentational children, and three signal - **`VocabularyExercisesWordListComponent`** - the presentational list screen. Receives the container's shared dataSource/selection/signals as inputs and forwards user intents as outputs; wires its template's paginator/sort onto the shared dataSource via `@ViewChild` setters. - **Signal stores** - `ReviewSessionStore` (queue, cursor, auto-mode interval, per-word `ratingMap`, progress computeds), `SpellingSessionStore` (queue, per-letter reveal state, results), and `QuizSessionStore` (single-choice question queue, per-question answer state, results). Plain `@Injectable()` classes (no state library), provided in the container's `providers` so the container and session screens share one instance per page instance. They are imported directly, not via the barrel. - **Session screens** - `review-session`, `spelling-session`, `spelling-result`, `quiz-session`, and `quiz-result` components inject their store directly (no state inputs) and each registers a `document:keyup` `@HostListener`, so keystroke handling only lives while that screen is mounted. -- **Dialogs** - one mode-driven select dialog (By Count / Free Selection / By Word), plus `reviewoptions`, `spellingoptions`, `worksheetoptions`, `quizoptions`, and the **shared** `filter` dialog (`src/app/shared/filter-dialog/` — `SharedFilterDialogComponent`, design in `docs/reusable-filter-dialog-design.md`), opened with the page's `VOCABULARY_FILTER_PROPERTIES` schema and an actslib `IFilterDefinition` seed; Submit returns the edited definition, Cancel/backdrop/Esc return `undefined`. It is a project-wide component (all four list pages — vocabulary, knowledge, Chinese and translate — now filter through it, each with its own `*_FILTER_PROPERTIES` schema), not a vocabulary-page file. Options dialogs round-trip `currentSettings` so reopening shows the last picks. -- **Filter pipeline** - free-text + the actslib **`IFilterDefinition`** (word/rating conditions in AND/OR-joined, nestable groups, produced directly by the shared dialog — no page-side tree model or translation step) combine into a `VocabularyListFilter` (`interfaces/vocabulary.ts`), serialized as JSON into `MatTableDataSource.filter`; the row predicate delegates to the pure `matchVocabularyListFilter()` (closing over the rating map), which runs `FilterUtility.MatchFilter` against a case-folded target `{ enword, cnword, rating }` (string condition values are folded by each property's `prepareValue` at dialog emit time; free-text stays a hand-written cross-field check). After async rating loads, re-assign `dataSource.filter = dataSource.filter` to force re-filtering. +- **Dialogs** - one mode-driven select dialog (By Count / Free Selection / By Word), plus `reviewoptions`, `spellingoptions`, `worksheetoptions`, `quizoptions`, and the **shared** `filter` dialog (`src/app/shared/filter-dialog/` — `SharedFilterDialogComponent`, design in `docs/reusable-filter-dialog-design.md`, contract in `docs/filter-hierarchy-contract.md`), opened with the page's `VOCABULARY_FILTER_PROPERTIES` schema and an actslib `FilterRoot` seed (a group definition, or a bare single condition); Submit returns the edited `FilterRoot` — a single-condition filter comes back as a bare `IFilterCondition` (`FilterUtility.Simplify` at the boundary) and an empty tree is not submittable (case 0 belongs to the Clear Filter button — a "new filter" dialog opens scaffolded in case 1 instead — one blank, selected condition; the editor tree IS the actslib root — the top level holds a SINGLE node (case 1: one condition row; case 2: one group row with its members nested; the wrapper is never rendered and holds at most one member, its join inert) — and the tree toolbar holds exactly three buttons enabled by the selected node's kind (nothing selected — the empty tree — arms both inserts, a condition arms delete only, a group arms all three); delete returns the selection to the parent group (or to nothing when the tree empties, where the inserts re-arm), so a node is always selected unless the tree is empty); Cancel/backdrop/Esc return `undefined`. It is a project-wide component (all four list pages — vocabulary, knowledge, Chinese and translate — now filter through it, each with its own `*_FILTER_PROPERTIES` schema), not a vocabulary-page file. Options dialogs round-trip `currentSettings` so reopening shows the last picks. +- **Filter pipeline** - free-text + the actslib **`FilterRoot`** (word/rating conditions in AND/OR-joined, nestable groups — a bare condition when the filter is a single condition; produced directly by the shared dialog, no page-side tree model or translation step) combine into a `VocabularyListFilter` (`interfaces/vocabulary.ts`), serialized as JSON into `MatTableDataSource.filter`; the row predicate delegates to the pure `matchVocabularyListFilter()` (closing over the rating map), which runs `FilterUtility.MatchFilter` against a case-folded target `{ enword, cnword, rating }` (string condition values are folded by each property's `prepareValue` at dialog emit time; free-text stays a hand-written cross-field check). After async rating loads, re-assign `dataSource.filter = dataSource.filter` to force re-filtering. - **Ratings** - list ratings live in a `contentRatingMap` signal; the review store captures only server-confirmed ratings and returns them on `quit()`, which the container merges back into the map. Temporary (uploaded) content gets a synthetic `LearningContent` with a negative id, and rating calls are disabled for it. - **Quiz exercise** - the Exercises-menu Quiz item reuses the shared word-queue selection (table selection, else filter + `prepareWordQueue`), then builds single-choice questions via the pure `buildVocabularyQuizQuestions()` (`interfaces/vocabulary.ts`): EN word -> pick the CN explanation among 4 candidates (`en2cn`), or CN explanation -> pick the EN word (`cn2en`). Distractors come from the visible (filtered) rows, deduplicated by displayed text; questions that cannot gather one distinct alternative are skipped. diff --git a/docs/filter-dialog-review.md b/docs/filter-dialog-review.md new file mode 100644 index 0000000..c510c8e --- /dev/null +++ b/docs/filter-dialog-review.md @@ -0,0 +1,310 @@ +# Shared Filter Dialog — Code Review (filter-hierarchy contract branch) + +Date: 2026-09-05 +Scope: the uncommitted `chroe/keepimprv-1` changes — the editor-tree normalization +to a SINGLE top node (invisible wrapper + case-1 bare-condition boundary) in +`src/app/shared/filter-dialog/`, the four list pages migrated to the shared +dialog, the new `docs/filter-hierarchy-contract.md`, and the touched docs/i18n. + +Method: multi-angle review (correctness ×3 lenses, docs, reuse, maintainability) +over the full diff, with claims verified against the actual code paths and the +actslib 0.6.83 library source (`FilterUtility.ToDefinition`/`Simplify`/`MatchFilter`); +the four `onDefineFilter` blocks were confirmed byte-identical by hash. The full +suite (44 files / 1841 tests) passed on the reviewed tree; no finding is a crash +in the normal edit → submit flow — they are contract-behavior drifts, misleading +UI copy, hardening gaps, and doc drift. + +**Totals: 2 HIGH · 4 MEDIUM · 4 LOW.** The dominant theme: the invisible wrapper +node (introduced by the 2026-09-04 single-top-node normalization) shifted depth +and root/perception by one everywhere `isRoot`/`depthOf` still counted the old +visible root — H1 and H2 are both consequences of that shift. + +**Progress: ALL FIXED 2026-09-05** — H1, H2, M1, M2, M3, M4, L1, L2, L3, L4 +(L1/L3 pulled forward — same file as the M1 edit). Doc-only findings carry no +test runs; grep sweeps confirmed no stale claims remain. + +**Final verification:** dev build clean; `ng lint` introduces no new errors or +warnings in any touched file (the one surviving warning in +`translate-exercises.component.ts:494` predates this work); full suite +**72 files / 1852 tests — all passing** (branch baseline 1841 + 11 new tests +from these fixes). + +--- + +## HIGH + +### H1. ✅ FIXED (2026-09-05) — The invisible wrapper occupies depth 1, silently tightening `maxDepth` by one level + +> **Status:** Fixed in `filter-dialog.component.ts` — `canAddGroup` now calls +> `depthOf(this.root(), node.id, 0)`: the unrendered wrapper is level 0, so the +> visible top node is level 1 and `maxDepth` is exactly the deepest group level +> the toolbar offers (matching `FilterDialogData.maxDepth` and contract §1). +> The old depth-cap test (nested group at `maxDepth: 2` disabled) still passes +> unchanged; two new tests close the gap that codified the tightened cap: the +> root group CAN add a nested group at `maxDepth: 2` (the contract's flagship +> `condA AND (condB OR condC)`) and CANNOT at `maxDepth: 1`. Component spec +> green: 24 tests. + +- `src/app/shared/filter-dialog/filter-dialog.component.ts:248` (`canAddGroup` → + `depthOf(this.root(), node.id, 1) < this.maxDepth`) +- Before the wrapper normalization, the user's root group WAS the tree root and + sat at depth 1, so `maxDepth: 4` (default) allowed root + 3 nested group + levels. Now `seedTree` returns an invisible scaffold wrapper as the root, so + the perceived root group sits at depth 2 — the deepest buildable group is one + level shallower than before *and* than the documented contract + (`FilterDialogData.maxDepth`: "deepest group level the toolbar offers; + default 4"; `docs/filter-hierarchy-contract.md` §1: "Nesting depth is a UI + cap ... `maxDepth` (default 4) only disables the '+ group' toolbar button at + the deepest level"). +- Scenario: a caller passes `maxDepth: 2` to cap nesting at one level. The + contract's own flagship example `condA AND (condB OR condC)` is no longer + expressible — the root group is at depth 2, so `2 < 2` disables "+ group" + into it. The depth-cap spec test only asserts the nested-group case of + `seedDef`, so it codifies the tightened cap instead of catching it. +- Fix: measure depth from the wrapper as level 0 (the wrapper is invisible and + never a row — visible row levels start at 1 on the root node), i.e. + `depthOf(this.root(), node.id, 0)`; add a spec asserting the root group CAN + take a child at `maxDepth: 2` and cannot at `maxDepth: 1`, and that the + deepest offered visible group level equals `maxDepth`. + +### H2. ✅ FIXED (2026-09-05) — The ≥2-members warning blames a "non-root" group on the row the user sees as the root + +> **Status:** Fixed — **message-only**, per the contract (§1 case 2: "every +> group branches — ≥2 children"; S3.2: auto-dissolve/hoist explicitly +> REJECTED — "gate + warn, no silent edits"), so the validation itself was +> correct and unchanged: `common.filterGroupNeedsTwo` is now position-neutral +> ("A condition group needs at least two members" / "条件组至少需要两个成员", +> en + zh; the dead `vocabularyExercises.*` copy is left for L4 to delete), +> and the stale taxonomy comments were rewritten — `validateTree`'s doc block, +> the `FilterTreeValidation.invalidGroupIds` field, the component class doc, +> and the model-spec test title ("the wrapper's exemption is STRUCTURAL; +> every rendered group, the top row included, must branch"). No behavior +> change; both filter-dialog specs green: 66 tests. + +- `src/app/shared/filter-dialog/filter-dialog.component.html:40,78` + the + string itself `common.filterGroupNeedsTwo` (`en.json:75`, `zh-CN.json:75`): + "A non-root condition group needs at least two members". +- `validateTree` still exempts only the wrapper (`walk(root, true)`), so the + single top GROUP node is flagged through the nested-group path. The flagging + is contract-correct; the message is not — on the root row it tells the user + about a group that "isn't root" while they are looking at the only row that + IS the root, and the detail-pane error repeats it. +- Scenario (documented growth path): delete to the empty tree → "+ group" → + the single root group row warns "A **non-root** condition group needs at + least two members". Same after building `(A AND B)` and deleting `B`. +- Fix: make the string position-neutral — "A condition group needs at least + two members" (en/zh) — and update the stale comments that still describe the + pre-normalization taxonomy: the `validateTree` doc block ("The root is + exempt ... 1 member is a plain single-condition filter" — that was the old + visible-root framing), `FilterTreeValidation.invalidGroupIds` ("root + exempt"), and the model-spec test title. + +--- + +## MEDIUM + +### M1. ✅ FIXED (2026-09-05) — A definition-shaped root missing `conditions` throws instead of degrading to case 0 + +> **Status:** Fixed in `filter-dialog-model.ts` — a shared `membersOf(def)` +> helper (`def.conditions ?? []`) now backs every walk over a caller's +> definition: `hasActiveFilterDefinition`, `seedTree` (`seedNode`/`seedTop`), +> `foldEnumOrGroup`, and `summarizeFilterDefinition` (`foldEnumParts` / +> `renderGroup`), mirroring actslib's own `Simplify`/`MatchFilter` treatment +> of a missing `conditions` as empty. A JSON-decoded `{ join }` seed now +> degrades to case 0 (inactive menu state, empty scaffold, empty summary) +> instead of throwing during change detection. Covered by 3 new tests in the +> model spec ("a definition missing the conditions key (M1)", root + nested +> cases). Model spec green: 45 tests. + +- `src/app/shared/filter-dialog/filter-dialog-model.ts` — + `hasActiveFilterDefinition` (:240 `root.conditions.some`), `seedTop` + (:366 `current.conditions.length`), `foldEnumOrGroup` (:277), and + `summarizeFilterDefinition`'s `foldEnumParts`/`renderGroup` loops, all + dereference `.conditions` unguarded. actslib's own `MatchFilter` treats a + missing `conditions` as empty (`Simplify` guards with + `definition.conditions && ...`), so `{ join: 'OR' }` — a legal decode from + persisted/URL-restored JSON — matches all rows server-side but crashes the + page that renders its menu label, or crashes `seedTree` when opening the + dialog. +- Fix: one internal `membersOf(def)` helper (`def.conditions ?? []`) used by + every walk, so a malformed seed degrades to case 0 / match-all consistently. + +### M2. ✅ FIXED (2026-09-05) — `docs/vocabulary-exercises-architecture.md` still documents the OLD submit contract + +> **Status:** Fixed — §7.3 now documents Submit `{ root: FilterRoot }` (bare +> `IFilterCondition` for a single-condition filter via `Simplify`), the +> single-top-node editor tree with the invisible wrapper, the corrected +> validation wording (every rendered group branches, top GROUP row included; +> empty tree not submittable — Clear Filter owns case 0), and the `maxDepth` +> semantics as fixed in H1; §8 / §8.1 / the §10.5 model snippets now use +> `FilterRoot` (verified against `interfaces/vocabulary.ts:268` and the actslib +> `IFilterDefinition` declaration — `conditions` is REQUIRED, so the guard in +> M1 is runtime hardening for JSON decodes, not a type change); the signal +> table row (:120) reads `signal`. Grep confirms no +> `IFilterDefinition`-only submit claims remain. Doc-only; no tests affected. + +- The as-built reference (designated by CLAUDE.md and cross-referenced by + `docs/filter-hierarchy-contract.md` §5) still says Submit returns + `{ root: IFilterDefinition }` (§7, ~:405), `root: IFilterDefinition` in the + `VocabularyListFilter` snippets (:420, :552), and `filterDefinition = + signal` (:120) — untouched by this diff. +- Failure scenario: a maintainer wiring the next consumer (filter persistence, + a fifth page) follows it literally and dereferences `result.root.conditions` + on the dialog result — a single-condition filter now crosses the boundary as + a bare `IFilterCondition` with no `conditions` field → TypeError at runtime. +- Fix: update those sections to the `FilterRoot` contract (bare condition for + case 1 via `Simplify`; `filterDefinition` signals typed `FilterRoot`). + +### M3. ✅ FIXED (2026-09-05) — `docs/reusable-filter-dialog-design.md` body still teaches the DELETED invariants + +> **Status:** Fixed — the body now matches the appended correction bullets: +> D7 rewritten ("root exempt" is now STRUCTURAL, covering only the invisible +> wrapper; every rendered group must branch; case 0 not submittable); §9's +> intro states the three-case taxonomy (contract §1) and the full gate +> `canSubmit = !emptyTree && noMissingValue && noInvalidGroups`; §6.2's +> `seedTree`/`emitTree` rows rewritten (single-top-node normalization; the +> page never receives `conditions: []` from the dialog — the empty-root +> emission is scoped to the pure function); §13's test-strategy line scoped +> likewise; the `maxDepth` bullet now states the H1-corrected visible-level +> semantics; and the status header reads "all four list pages migrated +> (Phases 1–5)" instead of "Phases 3–5 pending" (a same-class drift the +> review's grep pass surfaced). Doc-only; grep sweep clean. + +- The diff appended correction bullets at the top but left the body: §9 "root + exempt: 0 = clear-filter" (:438), D7 "root exempt" (:119), the `emitTree` + contract row "root may emit `conditions: []` (= match-all = cleared + filter)" (:290), and the required invariant "empty root → `conditions: + []`" (:563) — all contradicting the new gate (case 0 is not submittable; + one-line spec assertions for the old behavior were deleted in this diff). +- Failure scenario: someone implementing/testing `validateTree` from §9 writes + `expect(validateTree(rootOf(), schema).canSubmit).toBe(true)` — the exact + assertion this diff removed — and gets RED; or "fixes" the code to match §9, + reopening the path where an emptied tree silently installs a match-all + filter from inside the dialog, bypassing the Clear Filter button that owns + case 0. +- Fix: rewrite the stale sections to the three-case taxonomy (contract §1). + +### M4. ✅ FIXED (2026-09-05) — The dialog-open block and the empty-definition factory each have four owners + +> **Status:** Fixed — new `src/app/shared/filter-dialog/filter-dialog-launcher.ts` +> exports `FILTER_DIALOG_CONFIG` + `openFilterDialog()` (seed wiring, the +> standard sizing/animation config, the `takeUntilDestroyed` subscription and +> the Cancel guard, all once), and a single `emptyFilterDefinition()` now +> lives in `filter-dialog-model.ts` (barrel-exported). All four containers' +> `onDefineFilter` collapse to an 8-line launcher call; the four +> `emptyXFilterDefinition()` factories are deleted from `interfaces/` (with +> their now-unused `FilterJoinType`/`IFilterDefinition` imports) and +> `vocabulary.spec.ts` inlines the case-0 literal. The design doc §10 page-side +> snippet (still showing the old `width: '880px'` hand-rolled form) was +> updated to the launcher shape. New `filter-dialog-launcher.spec.ts` (4 +> tests: config/seed pass-through, `maxDepth`/`titleKey` overrides, Submit → +> `onApplied`, Cancel → untouched). Dev build clean; the four page specs + +> `vocabulary.spec` + launcher spec green: 426 tests. + +- The 28-line `onDefineFilter` block is byte-identical across all four + containers (vocabulary :320, knowledge, chinese, translate), as are the four + `emptyXFilterDefinition()` factories in the interface files + (`{ join: AND, conditions: [] }`). Every viewport/animation/result-handling + tweak (this diff itself fanned the `width: '1100px'` pair into four files) is + a 4-file edit, and a missed page silently diverges; a fifth page + copy-pasting can drop the `result !== undefined` guard (Cancel must keep the + old filter) with no compile error. +- Fix: export `openFilterDialog()` (standard config + seed + Cancel-guard + + `onApplied` callback) and one `emptyFilterDefinition()` from the shared + filter-dialog barrel; pages keep only their 5-line wiring, the interface + factories are deleted. + +--- + +## LOW + +### L1. ✅ FIXED (2026-09-05) — `summarizeFilterDefinition`'s ellipsis can split a surrogate pair + +> **Status:** Fixed — the cap now slices by code point +> (`Array.from(joined).slice(0, maxLength - 1).join('')`), so an astral-plane +> character at the cut lands whole-or-dropped, never half. Covered by 1 new +> model-spec test ("the ellipsis cut never splits a surrogate pair (L1)") +> using CJK Ext-B `𠀋` positioned exactly at the old UTF-16 cut; the existing +> BMP cap test still passes unchanged. Model spec green: 46 tests. + +- `filter-dialog-model.ts:827` slices by UTF-16 code unit. A menu summary + longer than `FILTER_MENU_MAX_LENGTH` containing an astral-plane character + (e.g. CJK Extension B `𠀋`) cuts between the surrogates → the label renders + `U+FFFD` ("?"). +- Fix: code-point-safe slice (`Array.from`/spread), assert in the cap test. + +### L2. ✅ FIXED (2026-09-05) — The toolbar delete button is always labeled "Remove group", even for a condition + +> **Status:** Fixed — `deleteSelectedLabelKey()` on the component returns +> `common.removeFilterCondition` when the selected node is a leaf and +> `common.removeFilterGroup` otherwise (group, or the disabled no-selection +> state); the button's `aria-label`/`title` follow it, and the new +> `common.removeFilterCondition` ("Remove condition" / "删除条件") was added to +> the `common` block of both locale files. Covered by 1 new component-spec +> test asserting both wordings. Component spec green: 25 tests. + +- `filter-dialog.component.html:21-22` keeps `common.removeFilterGroup` + although the kind-armed toolbar made this button the ONLY delete affordance + for plain conditions too — including the blank condition every "new filter" + dialog opens with. Screen-reader users hear "Remove group" to delete a + condition. +- Fix: label by selected kind (new `common.removeFilterCondition`, en "Remove + condition" / zh "删除条件"; group keeps the existing key). + +### L3. ✅ FIXED (2026-09-05) — The bare-condition wrap hand-copies actslib's `ToDefinition` while Submit uses the real `Simplify` + +> **Status:** Fixed — `summarizeFilterDefinition` now calls +> `FilterUtility.ToDefinition(root)` (actslib `FilterUtility.ts:207`), the +> library inverse of the `Simplify` used at Submit, so the two boundary +> conversions can no longer drift apart. Behavior-identical (verified: +> `ToDefinition` returns `{ conditions: [c] }` for a bare condition and the +> definition untouched otherwise) — the existing "renders a bare condition +> (case 1) like its wrapper" spec guards it. Model spec green: 46 tests. + +- `filter-dialog-model.ts:773` (`isFilterCondition(root) ? { conditions: [root] } : root`) + re-implements `FilterUtility.ToDefinition` (actslib `FilterUtility.ts:207`) + verbatim. The pair `ToDefinition`/`Simplify` evolves together in actslib; + Submit follows the library, this copy does not — after a spelling change the + menu label/preview would describe a different shape than `MatchFilter` + evaluates. +- Fix: `const def = FilterUtility.ToDefinition(root)` (import the value). + +### L4. ✅ FIXED (2026-09-05) — Eleven orphaned filter keys left under `vocabularyExercises.*` + +> **Status:** Fixed — the eleven dead scoped copies (`filterGroup`, +> `filterJoinHint`, `filterJoinSelect`, `addFilterGroup`, +> `removeFilterGroup`, `filterGroupNeedsTwo`, `filterCondition`, +> `filterDetailEmpty`, `filterSplitter`, `filterPreview`, +> `filterPreviewEmpty`) deleted from the `vocabularyExercises` block of both +> locale files, after a per-key reference sweep (zero non-shared uses in +> `.ts`/`.html`); `wordOpIsPhrase` KEPT (live `labelKey`, +> `interfaces/vocabulary.ts:193`) and `editWordFilter` kept (dead but the +> scoped `editXFilter` title-key convention; see note below). Both files +> parse as valid JSON; a grep for the deleted names now returns exactly 12 +> hits per file, all the live `common.*` owners. (The per-key reference sweep +> also confirmed `editWordFilter` is unreferenced — kept, consistent with the +> other pages' scoped `editXFilter` title-key convention; flagged there for a +> future i18n sweep.) + +- `en.json:189-199` / `zh-CN.json:146+`: `filterGroup`, `filterJoinHint`, + `filterJoinSelect`, `addFilterGroup`, `removeFilterGroup`, + `filterGroupNeedsTwo`, `filterCondition`, `filterDetailEmpty`, + `filterSplitter`, `filterPreview`, `filterPreviewEmpty` — leftovers of the + deleted per-page dialog (the shared dialog reads only `common.*`; + verified zero references). This diff had to update `filterDetailEmpty` in + FOUR places (common + dead copy × en + zh) to stay consistent — dead config + that looks live. +- Fix: delete the eleven verified-dead copies (keep `wordOpIsPhrase`); greps + for these keys then return only the live `common.*` owners. + +--- + +## Notes (reviewed, no action) + +- The core paths (`seedTop` normalization, `onDeleteSelected` selection + return, `buildInitialTree` scaffolding, the four pages' filter pipelines) + were traced clean by three independent correctness angles; the + `Simplify`-at-boundary round-trip (`emit → seed → emit` + `Simplify`) is + covered by the model spec. +- The one finder that reported against already-merged commit `e01e504` + (Angular 22 upgrade) was outside this diff's scope and discarded. diff --git a/docs/filter-hierarchy-contract.md b/docs/filter-hierarchy-contract.md new file mode 100644 index 0000000..ac95a7d --- /dev/null +++ b/docs/filter-hierarchy-contract.md @@ -0,0 +1,213 @@ +# Filter Hierarchy Contract — Observations & Suggestions + +Scope: the hierarchy rules of the shared filter dialog +(`src/app/shared/filter-dialog/`), as used by all four list pages. +Design source: `docs/reusable-filter-dialog-design.md` (§9, D1, D3, D7). +This note records the end-user mental model agreed in review (2026-09), how +it maps to the current code, how actslib's root type relates to it (refreshed +for actslib 0.6.83, which added `FilterRoot`), and three suggestions. +**Update 2026-09-03:** S3.1 and the case-0 submit gate have since been +adopted (see §2/§4 markers); the rest of the note is as reviewed. +**Update 2026-09-04:** the editor tree is normalized to a SINGLE top node — +the actslib root itself (bare/1-member seeds → one condition leaf; a 2+ +member definition → one root GROUP row); the toolbar is exactly three +buttons with kind-based enablement and the toolbar join select is gone +(see §2 marker) — §1's "visible rows" column follows that. + +## 1. The contract, stated positively (three cases) + +A submitted filter is a tree in exactly one of three shapes: + +| # | Shape | Editor tree (visible rows) | Emitted root | Semantics | +|---|---|---|---|---| +| 0 | **Empty** (no filter) | no rows (transient — deleting the one node; nothing is selected and the toolbar's inserts stand armed); Submit gated off | **not submittable** (Submit gate); the page's Clear Filter button sets `{ join, conditions: [] }` directly | match-all = the cleared-filter state; the menu offers "new filter", `hasActiveFilterDefinition()` returns false | +| 1 | **One node** | the single condition row — the root wrapper is never rendered (it holds at most one member), so the visible tree literally is one node | a bare condition `c` (`emitTree`'s wrapper passed through `Simplify` at Submit) | the whole hierarchy is a single condition; the root wrapper is invisible to the user and irrelevant to matching | +| 2 | **Group tree** | ONE root group row (the seed's definition itself, per 2026-09-04 normalization) with its member rows nested under it | nested definitions as parenthesized groups | leaves are conditions; every group branches — **≥2 children**; e.g. `condA AND (condB OR condC)`; the root group's join edits in the detail pane | + +Case 1 has **two interchangeable spellings**. actslib 0.6.83 defines +`FilterRoot = IFilterCondition | IFilterDefinition` (`FilterUtility.ts:113`), +and `MatchFilter`/`FilterList` accept either form at the top: a bare condition +matches exactly like the 1-member wrapper `{ conditions: [c] }`. +`FilterUtility.ToDefinition(c)` (`:207`) turns the bare form into the wrapper; +`FilterUtility.Simplify(def)` (`:222`) turns a 1-member wrapper back into the +bare form. **As of 2026-09-03 the dialog transports the bare spelling** (§2, +S3.1): a single-condition filter leaves Submit as a bare `IFilterCondition`, +and either spelling may seed the dialog. + +Rules that complete the picture for case 2: + +- **Mixed members are valid.** A group may hold condition leaves *and* nested + sub-groups side by side. Do not "simplify" the contract into "uniform member + type per group" — the pages' matchers and `FilterUtility.MatchFilter` all + support mixed trees, and the dialog relies on it. (actslib names this member + union `FilterMember`, `:103` — the `IFilterCondition | IFilterDefinition` + the app currently writes out inline.) +- **Nesting depth is a UI cap, not a validity rule.** `maxDepth` (default 4) + only disables the "+ group" toolbar button at the deepest level; + `seedTree` preserves structure at any depth, so a hand-built deeper seed + stays editable and submittable. + +## 2. Mapping to the current implementation + +The code enforces the three cases above as the positive taxonomy, with two +post-review changes (2026-09-03): the dialog no longer submits case 0, and +case 1 now travels in its bare spelling. + +- `validateTree` (`filter-dialog-model.ts`): flags leaves missing values and + nested groups with `< 2` members (`invalidGroupIds`); the root is exempt + from that rule via `walk(root, true)` (1 member = case 1), and a new + `emptyTree` flag rejects a 0-member root — **case 0 is not submittable**, + clearing belongs to the pages' Clear Filter button. +- Submit is gated on `canSubmit`, so a filter handed to a page never contains + a 0-member tree or a 0- or 1-member *nested* group. +- `buildInitialTree` (component) opens an empty seed — the "new filter" case — + scaffolded with **one blank condition**, selected, so the dialog opens in + case 1: exactly one node (the detail pane lands on the condition editor + for scaffolded *and* seeded case-1 trees alike). The editor therefore only + ever presents cases 1/2; until the leaf is filled, the missing-value rule + keeps Submit disabled, so case 0 never even becomes reachable at the gate. +- **(Revised 2026-09-04)** `seedTree` normalizes the seed to a SINGLE top + node — the actslib root itself: a bare condition or any chain of + 1-member definition wrappers seeds one leaf (all case-1 spellings seed + identically); a 2+ member definition becomes one GROUP node carrying the + definition's join (case 2); an empty/absent seed yields no node. The + wrapper remains the S2 recommendation — an invisible scaffold holding 0 + or 1 members, its join inert by construction and unwrapped by `Simplify` + at Submit — so `treeData`'s top level renders at most one row and the + navigator mirrors the root exactly. The toolbar is **exactly three + buttons** (+ condition, + group, delete; the old toolbar join select is + gone — every rendered group row, the top one included, edits its join in + the detail pane), enabled by the selected node's kind: nothing selected + (the empty tree) arms the two inserts (they land on the scaffold as THE + node), a condition arms delete only, a group arms all three (it is the + insert target). `onDeleteSelected` moves the selection to the parent + group — or to nothing when the tree just emptied — so a node is selected + unless the tree is empty; growth from case 1 runs delete → + group → + + condition. +- `emitTree` returns an `IFilterDefinition`, but `onSubmit` now closes with + `FilterUtility.Simplify(emitTree(...))`: a case-1 filter leaves the dialog + as a **bare condition** (S3.1 adopted). `FilterDialogData.root` and + `FilterDialogResult.root` are `FilterRoot`, `seedTree` folds a bare + condition into root+leaf (both spellings seed identically), and + `hasActiveFilterDefinition` / `summarizeFilterDefinition` accept both. + Pages type their `filterDefinition` as `FilterRoot`; the pages' matchers + already handed any `FilterRoot` to `MatchFilter` unchanged. +- Both group shapes the dialog **synthesizes** obey case 2 by construction: + the enum multi-choice with N>1 values emits an OR group of N `Equal`s + (always ≥2 members, `emitLeaf` at `filter-dialog-model.ts:420`), and a + 1-value enum emits a bare condition instead. Custom (valueless) operators + emit bare conditions. +- Transient violations are reachable *during editing* only: "+ group" inserts + a **single childless group node** (one click, one node — no seeded + placeholder) which carries the ⚠ hint until its first two members are added, + or a hand-built seed containing a 1-member sub-group (`seedTree` preserves + it; the gate blocks Submit until the user fixes it). + +## 3. How actslib's root type relates to the contract + +Earlier actslib (`IFilterDefinition`-only at the top level) could not carry +case 1 as a bare condition — it had to travel as the 1-member wrapper. That +limitation is **gone in 0.6.83**: + +- `FilterRoot = IFilterCondition | IFilterDefinition` (`:113`) and + `FilterMember` (`:103`) are now exported. +- `MatchFilter` / `FilterList` (`:160`, `:175`) accept a `FilterRoot` (or a + `FilterRoot[]`, joined by AND), normalizing a bare condition internally via + `ToDefinition`. +- `ToDefinition(c)` (`:207`) wraps a bare condition into `{ conditions: [c] }`; + `Simplify(def)` (`:222`) unwraps a 1-member *root* group back to its member + (once, at the root — nested groups are left alone, since pruning those is + caller policy). They are exact inverses at the root. + +The wrapper is still harmless, and for the same reason: `MatchFilter` on a +1-member definition returns that member's result regardless of `join` +(`:175-198`), so the bare condition and its wrapper evaluate identically. That +is what makes the two case-1 spellings interchangeable. The +`IFilterDefinition` source comment (`:84-93`) now states this very three-case +taxonomy and points at `FilterRoot` — actslib adopted §1's positive +formulation on its own. + +The round-trip is still lossless: `seedTree` copies whatever shape it is handed, +so a re-opened case-1 filter (wrapper **or** bare) shows the single condition, +and re-emitting is stable. The one place the wrapper still *matters* is +`emitTree`: it prunes only **empty** sub-groups; a 1-member nested group is +emitted as-is if the model is driven directly (unit tests) instead of through +the Submit gate. The gate is what keeps case 2's "≥2 children" true for emitted +output — remember this when asserting on hand-built trees in model specs. + +Consequence for tests: assert at the right level. Editor-tree assertions +expect root + leaf (the wrapper node exists in the model); `emitTree` +assertions still expect the 1-member wrapper (the pure model function is +unchanged), while **Submit-boundary** assertions expect the bare condition — +`onSubmit` runs `Simplify` over `emitTree`'s result. Mixing the two levels is +the easy mistake here. + +## 4. Suggestions + +**S1 — Adopt the positive formulation in the docs (recommended, no behavior +change).** The upstream half has landed: actslib's own `IFilterDefinition` +comment now uses exactly this taxonomy and names the union `FilterRoot`. The +remaining work is our docs — rewrite the §9 / D7 sentence in +`reusable-filter-dialog-design.md` and the `CLAUDE.md` filter-pipeline bullets +from "nested groups ≥2, root exempt" to the three cases of §1, and state the +mixed-members rule explicitly (§1) so future refactors don't narrow it. The +exemption framing reads like a special case; the taxonomy reads like the model +it actually is. + +**S2 — Keep `SharedFilterDialogNode` as the root type (recommendation: do not +re-model).** Letting the *editor* root be a bare leaf (union leaf | node) would +match case 1 in the type, but every recursive helper (`mutateNode`, +`parentIdOf`, `depthOf`, `appendMember`, `removeMember`, template recursion) +would grow a root-is-leaf branch, and case 0 still needs its own shape — +≈10 special cases traded for 1. actslib now offering a union `FilterRoot` does +not change this: `FilterRoot` is a *transport* type, and `ToDefinition` / +`Simplify` already bridge the editor's wrapper to it at the boundary, so the +single `isRoot` line in `validateTree` stays the cheapest carrier of the +taxonomy in the editor. **2026-09-04 note:** upheld — §2's single-top-node +normalization keeps the wrapper as the editor root type (0 or 1 members, +join inert); the case-1 "root node" the user sees is the one leaf it holds. + +**S3 — Optional changes at the emit boundary and during editing (decisions +open).** Two separable ideas: + +1. *Transport (now possible, one line):* if you want case 1 to leave the dialog + as a bare condition rather than the 1-member wrapper, wrap the emit in + `FilterUtility.Simplify(emitTree(...))` at the Submit boundary and widen the + page-facing type to `FilterRoot`. Purely cosmetic — evaluation is identical + (§3) — so only do it if the bare form reads better in persisted filters. + Otherwise leave the wrapper; it is stable and lossless. + **ADOPTED 2026-09-03** (§2): Submit runs `Simplify`; seed/result and the + page signals are `FilterRoot`; a companion gate rejects the empty tree + (case 0 stays the Clear Filter button's job). +2. *Editing hardening:* two paired edits would render `filterGroupNeedsTwo` + unreachable in normal use — `removeMember` auto-dissolves a nested group + left with exactly one member (hoist it into the parent), and "+ group" seeds + two empty rows instead of zero. Trade-off, explicitly: this quietly rewrites + the tree the user built, while the dialog's stated policy (D7: validate at + the gate, no silent edits) prefers the transparent ⚠ hint. Users + mid-construction ("isolate this one condition now, add its sibling later") + would find the group dissolved under them. The gate-and-warn status quo is + defensible; adopt the hardening only if testing feedback shows the warning + is more annoyance than guidance. If adopted, keep `validateTree`'s ≥2 rule + as the backstop for hand-built seeds. + **PARTIALLY RESOLVED 2026-09-04:** testing rejected the old behavior from a + different angle — seeding *one* empty leaf made "+ group" add two nodes per + click and start every group already invalid. The seed is now **zero** rows + (a childless group node: one click, one node, honest ⚠ until filled); + "seeds two" was rejected as two phantoms per click, and `removeMember` + auto-dissolve stays unadopted (D7: gate + warn, no silent edits). + +## 5. Reference + +- `src/app/shared/filter-dialog/filter-dialog-model.ts` — `validateTree`, + `seedTree`, `emitTree` (returns `IFilterDefinition`), `emitLeaf`, + `hasActiveFilterDefinition` +- `src/app/shared/filter-dialog/filter-dialog.component.ts` — `canSubmit` + gate, `onSubmit`, `isNodeInvalid` +- `docs/reusable-filter-dialog-design.md` — D1/D3/D7, §7.3, §9 +- `docs/vocabulary-exercises-architecture.md` — filter pipeline §, line ~433 +- `node_modules/actslib/src/lib/utility/FilterUtility.ts` — evaluation + semantics and the root taxonomy: `IFilterDefinition` (`:95`, comment + `:84-93`), `FilterMember` (`:103`), `FilterRoot` (`:113`), `FilterList` + (`:160`), `MatchFilter` (`:175-198`), `ToDefinition` (`:207`), `Simplify` + (`:222`) diff --git a/docs/reusable-filter-dialog-design.md b/docs/reusable-filter-dialog-design.md index 2b489b7..0e06b60 100644 --- a/docs/reusable-filter-dialog-design.md +++ b/docs/reusable-filter-dialog-design.md @@ -1,11 +1,11 @@ # Design: Shared Filter Dialog (reusable condition-tree filter editor) -Status: **implemented; vocabulary page migrated (Phase 1 + 2)** — the shared -dialog lives in `src/app/shared/filter-dialog/` and the vocabulary page uses it -(`VOCABULARY_FILTER_PROPERTIES` schema in `interfaces/vocabulary.ts`); the old +Status: **implemented; all four list pages migrated (Phases 1–5)** — the +shared dialog lives in `src/app/shared/filter-dialog/` and the vocabulary, +knowledge, Chinese and translate pages each filter through it with their own +`*_FILTER_PROPERTIES` schema (`interfaces/`); the old `vocabulary-exercises-filter-dialog.*` files and the `VocabularyFilterGroup` -model are deleted. Phases 3–5 (knowledge, Chinese, translate adoption) are -pending. Implementation deltas from this doc, all deliberate: +model are deleted. Implementation deltas from this doc, all deliberate: - the leaf holds **three value slots** (`single` / `between` / `choices`) rather than the five named fields of §6.1; - the date editor is a native `` (no `matDatepicker`, so the @@ -13,7 +13,36 @@ pending. Implementation deltas from this doc, all deliberate: - summaries render **comparison symbols** (`>=`, `<`) for numeric/date properties and word labels otherwise, keeping rating phrases compact; - the join word in summaries is **translated** (`common.joinAnd`/`joinOr`) - rather than hardcoded English, matching the localization-first rule. + rather than hardcoded English, matching the localization-first rule; +- (2026-09-03) seed and result are actslib **`FilterRoot`**, not + `IFilterDefinition`: Submit runs `FilterUtility.Simplify(emitTree(...))` so a + single-condition filter (case 1) crosses the boundary as a **bare + condition**, and a bare-condition seed folds back into the root+leaf editor + tree (see `docs/filter-hierarchy-contract.md` §2); +- (2026-09-03) **the empty tree (case 0) is not submittable** — the dialog's + contract is cases 1 and 2 only; clearing a filter is the pages' Clear + Filter button (the exemption in D7 now covers only the invisible wrapper, + structurally; `validateTree` flags `emptyTree`). An empty seed opens **scaffolded with one blank + condition** (selected), so the editor only ever presents cases 1/2; +- (2026-09-04) **the editor tree IS the actslib root** — `seedTree` + normalizes any seed to a SINGLE top node: a bare condition (or a chain of + 1-member definition wrappers, the case-1 spellings) seeds one condition + LEAF; a 2+ member definition becomes one GROUP node carrying the seed's + join (case 2); an empty/absent seed leaves no node (and the "new filter" + scaffold fills in one blank condition — case 1 on open). The wrapper stays + the S2 recommendation — an invisible `SharedFilterDialogNode` scaffold + holding 0 or 1 members, its join inert by construction (no union type); + `Simplify` unwraps it at Submit exactly as before. Consequences for the + UI: the tree toolbar is **exactly three buttons** (+ condition, + group, + delete — the toolbar join select is gone, since every rendered group row, + the top one included, edits its join in the detail pane), enabled by the + selected node's kind — nothing selected (empty tree) arms the two + inserts, a condition arms delete only, a group arms all three (it becomes + the insert target) — and delete returns the selection to the parent group + (or to nothing when the tree empties), so **a node is always selected + unless the tree is empty**. Growth from case 1 runs through the empty + state: delete → + group → + condition; "+ group" keeps one-click-one-node + (a childless OR group carrying the ⚠ hint). Author: Claude Code session 2026-08-30 Base implementation: the (now deleted) vocabulary filter dialog, which this @@ -81,13 +110,13 @@ dialog owns tree editing, validation, and the `IFilterDefinition` I/O. | # | Decision | Rationale | |---|---|---| -| D1 | Seed + result are **actslib `IFilterDefinition`** | Pages already evaluate it (`FilterUtility.MatchFilter`); kills every page-specific dialog model and the `VocabularyFilterGroup` ↔ definition translation. The dialog is generic precisely because its I/O is the evaluator's language. | +| D1 | Seed + result are **actslib filter roots** — `IFilterDefinition` since widened to `FilterRoot` (2026-09-03) | Pages already evaluate it (`FilterUtility.MatchFilter`); kills every page-specific dialog model and the `VocabularyFilterGroup` ↔ definition translation. The dialog is generic precisely because its I/O is the evaluator's language. | | D2 | **Property schema** passed via `MAT_DIALOG_DATA`, operators defaulted per kind from actslib's matrix, narrowed by whitelist | "allowed options per property" without every page re-listing `>`/`>=`/… ; whitelist still controls what's *offered* (e.g. rating offers only `=`). | | D3 | Enum multi-select compiles to **one leaf that emits an OR-of-`Equal` group**; seeds fold back | actslib has no `In` operation; OR-of-equals is the only faithful encoding, and `enumValues` per condition keeps actslib's enum validation. Fold-back keeps round-trips editable (§7.3). | | D4 | Valueless custom operators via **`customOperators` hook** (`emit` + `recognize`) | Vocabulary's `isPhrase` (→ `Contains ' '`) is app semantics actslib can't express; the hook keeps the dialog reusable without hardcoding word knowledge (§7.4). | | D5 | Keep the vocabulary editor-state pattern: numeric-id nodes, **reference `trackBy`**, **id `expansionKey`**, all edits **immutable through the root signal** | These are load-bearing CDK facts, not style choices (see §6.3 and the bug history); encoding them in the shared component prevents re-introducing them page by page. | | D6 | `prepareValue?` hook per property for case-folding / trimming | actslib string comparison is case-sensitive; the vocabulary page lowercases folded values *and* folded row fields. Keeping the hook on the property lets the page decide match semantics while the dialog stays content-agnostic (§8.4). | -| D7 | Validation rules move into the dialog model, same contract as today: blank/missing values and non-branching nested groups block Submit; root exempt | Just implemented in the vocabulary dialog; promoted verbatim to shared code (§9). | +| D7 | Validation rules move into the dialog model: blank/missing values and non-branching groups block Submit. Amended by the 2026-09-03/04 normalization: EVERY rendered group (the single top GROUP row included) must have ≥2 members — the "root exempt" exemption now applies only to the invisible wrapper, structurally (it holds 0-or-1 nodes; 1 = case 1), and an empty tree (case 0) is not submittable at all (§9) | Just implemented in the vocabulary dialog; promoted verbatim to shared code (§9). | --- @@ -257,8 +286,8 @@ export interface SharedFilterDialogNode { | function | role | |---|---| -| `seedTree(def: IFilterDefinition \| undefined, schema): SharedFilterDialogNode` | copy-in: conditions → leaves (fold-back: custom `recognize`, Between, enum OR-of-equals → one multi-choice leaf, single value); nested groups → nodes; **structure preserved at any depth**; never mutates the caller's def | -| `emitTree(root, schema): IFilterDefinition` | Submit output: leaves → conditions/groups (§7 dispatch); drops nothing (validation already guarantees completeness); root may emit `conditions: []` (= match-all = cleared filter) | +| `seedTree(root: FilterRoot \| undefined, schema): SharedFilterDialogNode` | copy-in: normalizes the seed to a SINGLE top node (2026-09-04 — bare condition or 1-member wrapper chain → one condition LEAF; 2+ member definition → one GROUP node carrying its join; empty/absent → no node); conditions → leaves (fold-back: custom `recognize`, Between, enum OR-of-equals → one multi-choice leaf, single value); nested groups → nodes; **structure preserved at any depth**; never mutates the caller's def | +| `emitTree(root, schema): IFilterDefinition` | pre-Submit emission: leaves → conditions/groups (§7 dispatch); drops nothing on a validated tree (the gate guarantees completeness; empty sub-groups appear only when the pure function is driven directly, e.g. unit tests). The **Submit boundary** passes it through `FilterUtility.Simplify`, so a single-condition filter leaves as a bare condition — case 0 is gated off (§9) and the page never receives `conditions: []` from the dialog | | `insertMember / deleteMember / patchNode / patchLeaf` | the vocabulary `mutateNode`/`replaceRow` immutables, generalized: every edit returns a new object along the mutation path | | `emptyLeaf(schema): SharedFilterDialogLeaf` | new row = first property, its first operator, blank values | | `validateTree(root, schema): ValidationState` | `hasMissingValue` + `invalidGroupIds` (§9) | @@ -406,14 +435,20 @@ drop it; root preselected. ## 9. Validation (Submit gate) `validateTree` returns per-leaf "missing value" flags + `invalidGroupIds` -(nested groups with `< 2` members; **root exempt**: 0 = clear-filter, 1 = -single-condition filter). A leaf is missing its value when the dispatch -selects an input and it is blank (`textValue.trim() === ''`, -`numberValue/dateValue == null`, any Between bound null, -`selectedChoices.length === 0`), or when Between has `low > high`. - -- **Submit button** `[disabled]="!canSubmit()"` — the rule from the vocabulary - session: `canSubmit = noMissingValue && noInvalidGroups`. +(**every rendered group** with `< 2` members — the single top GROUP row +included) + an `emptyTree` flag. This is the three-case taxonomy of +`docs/filter-hierarchy-contract.md` §1 stated as a gate: case 1 (a lone +condition leaf) and case 2 (a branching group tree) submit; the invisible +wrapper node is exempt from the branch rule only STRUCTURALLY — it holds at +most one node by construction; case 0 (a 0-member tree) is **not +submittable** — clearing is the pages' Clear Filter button, so the dialog can +never install a match-all filter from inside itself. A leaf is missing its +value when the dispatch selects an input and it is blank +(`textValue.trim() === ''`, `numberValue/dateValue == null`, any Between +bound null, `selectedChoices.length === 0`), or when Between has `low > high`. + +- **Submit button** `[disabled]="!canSubmit()"` — the gate extended to the + taxonomy: `canSubmit = !emptyTree && noMissingValue && noInvalidGroups`. - Offending rows get the invalid class + `error` icon (tree), and the detail pane shows the matching hint below the offending control; group hints reuse `vocabularyExercises.filterGroupNeedsTwo`… → promoted to @@ -422,22 +457,26 @@ selects an input and it is blank (`textValue.trim() === ''`, validation prevents submitting one, so `emitTree` becomes total and the "empty group would match everything" hazard is blocked at the gate instead of cleaned up afterwards. `Cancel` still mutates nothing. -- `maxDepth` disables "+ group" at the deepest level (default 4), same as - `VOCABULARY_FILTER_DIALOG_MAX_DEPTH` today. +- `maxDepth` disables "+ group" at the deepest level (default 4), counted in + VISIBLE group levels: the invisible wrapper is level 0, the top row is + level 1, so the deepest group the toolbar offers is exactly level + `maxDepth` (an off-by-one here silently eats one nesting level — see + `docs/filter-dialog-review.md` H1). ## 10. Evaluation contract (page side) -Pages apply the result like the vocabulary page does today: +Pages open the dialog through the shared `openFilterDialog` launcher +(`filter-dialog-launcher.ts`) — sizing/animation, the seed and the Cancel +guard live there once, so a fifth page cannot fan out a drift (review M4): ```ts onDefineFilter(): void { - this.dialog.open(SharedFilterDialogComponent, { - data: { properties: PAGE_FILTER_PROPERTIES, root: this.filterDefinition() }, - width: '880px', - }).afterClosed().pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(result => { - if (result) { this.filterDefinition.set(result.root); this.applyCurrentFilter(); } - }); + openFilterDialog(this.dialog, { + destroyRef: this.destroyRef, + properties: PAGE_FILTER_PROPERTIES, + current: this.filterDefinition(), + onApplied: root => { this.filterDefinition.set(root); this.applyCurrentFilter(); }, + }); } ``` @@ -531,7 +570,8 @@ needed, update CLAUDE.md's dialog list. - seed fold-back: custom-ops, Between, OR-of-`Equal`→multi-choice, mixed nesting depth preserved, caller's def never mutated; - emit: every dispatch row of §7, `prepareValue` applied (incl. skipped for - custom ops), enum 0/1/N behavior, empty root → `conditions: []`; + custom ops), enum 0/1/N behavior, empty root → `conditions: []` (the pure + function only — Submit gates case 0, §9); - round-trip: `emitTree(seedTree(emitTree(seed))) === emitTree(seed)` for representative trees (the vocabulary spec's round-trip tests promoted); - `validateTree` matrix; `summarize` (parens, cap, choice/between rendering). diff --git a/docs/vocabulary-exercises-architecture.md b/docs/vocabulary-exercises-architecture.md index f95a2d1..c6b9112 100644 --- a/docs/vocabulary-exercises-architecture.md +++ b/docs/vocabulary-exercises-architecture.md @@ -117,7 +117,7 @@ The container is the single source of truth for cross-cutting state and the orch | `selection` | `SelectionModel(true, [])` | Shared selection; read by both the word-list and queue prep. | | `contentRatingMap` | `signal(new Map())` | Per-itemId rating for the loaded file. Replaced (not mutated) on every update so OnPush consumers see a new reference. | | `freeText` | `signal('')` | Live free-text filter term. | -| `filterDefinition` | `signal` | The actslib condition definition from the shared filter dialog (word/rating leaves in AND/OR-joined groups); starts at `emptyVocabularyFilterDefinition()`. | +| `filterDefinition` | `signal` | The actslib filter root from the shared filter dialog — a group definition (word/rating leaves in AND/OR-joined groups), or a bare condition for a single-condition filter; starts at `emptyVocabularyFilterDefinition()` (case 0). | | `listFilterCriteria` | `private VocabularyListFilter \| null` | Parsed form of the active filter; kept in sync with `dataSource.filter` so the row predicate does not `JSON.parse` per row. `null` = no filter. | | `studyContentId` | `number` | Backend `ContentId` of the loaded file. Temp uploads get a **negative** id, which disables every `studyContentId > 0` rating guard. | | `reviewSetting` / `spellingSetting` / `quizSetting` / `worksheetSetting` | option objects | Persisted settings round-tripped by the options dialogs. | @@ -402,13 +402,13 @@ The vocabulary page no longer owns a filter dialog. The Filter menu opens the pr - `enword` / `cnword` — string properties with the four match operators (`BeginsWith`/`Contains`/`Equal`/`EndsWith`); emitted values are trimmed + lower-cased via each property's `prepareValue` hook. `isPhrase` survives as a **custom (valueless) operator**: it emits `Contains ' '` and folds back from it via `recognize`, so the phrase leaf stays editable across dialog round-trips. (A `notPhrase` variant was dropped: actslib `FilterUtility` has no negation.) - `rating` — a number property with the five comparison operators (`>= > = <= <`), 0–5 range. Rating is not a row field; the predicate passes it in the synthesized target (§8). -Word and rating conditions mix freely in the same group — that's what makes cross-dimension OR (`word starts with a OR rating = 5`) expressible; groups nest to any depth (the dialog caps only how deep "+ group" goes, default 4 levels; the evaluator is unbounded). The dialog edits the actslib definition **natively**: seed = the definition in effect, Submit = `{ root: IFilterDefinition }`, `Cancel`/backdrop/Esc = `undefined` (caller keeps the previous filter). Validation gates Submit (missing values; nested groups must have ≥2 members, root exempt — invalid groups are flagged in the tree), the master/detail layout is `mat-tree` + splitter + detail editor with the enum multiple-choice and two-input Between editors, and every edit flows through the dialog's `root` signal. See the design doc for the full UI contract. +Word and rating conditions mix freely in the same group — that's what makes cross-dimension OR (`word starts with a OR rating = 5`) expressible; groups nest to any depth (the dialog caps only how deep "+ group" goes, default 4 visible levels counting the root group as level 1; the evaluator is unbounded). The dialog edits the actslib root **natively**: seed = the `FilterRoot` in effect (a definition, or a bare condition), Submit = `{ root: FilterRoot }` — a single-condition filter leaves as a **bare `IFilterCondition`** (the emitted tree passed through `FilterUtility.Simplify` at the boundary), `Cancel`/backdrop/Esc = `undefined` (caller keeps the previous filter). The editor tree IS the root: its top level holds a single node (case 1: one condition row; case 2: one group row with its members nested), and an internal, never-rendered wrapper above it is invisible to the user. Validation gates Submit (missing values; every rendered group — the single top GROUP row included — must branch with ≥2 members, invalid groups flagged in the tree; the empty tree is **not** submittable — case 0 belongs to the page's Clear Filter button), the master/detail layout is `mat-tree` + splitter + detail editor with the enum multiple-choice and two-input Between editors, and every edit flows through the dialog's `root` signal. See the design doc and `docs/filter-hierarchy-contract.md` for the full contract. --- ## 8. Filter pipeline -All filtering decisions flow through one pure function — `matchVocabularyListFilter(item, rating, filter)` in `interfaces/vocabulary.ts` — and nothing else grows private matching logic. The condition **definition** is an actslib `IFilterDefinition` produced directly by the shared filter dialog (no page-side translation step) and evaluated by `FilterUtility.MatchFilter`. +All filtering decisions flow through one pure function — `matchVocabularyListFilter(item, rating, filter)` in `interfaces/vocabulary.ts` — and nothing else grows private matching logic. The condition **root** is an actslib `FilterRoot` (a group definition, or a bare condition for a single-condition filter) produced directly by the shared filter dialog (no page-side translation step) and evaluated by `FilterUtility.MatchFilter`, which accepts both spellings. ### 8.1 Composition @@ -417,10 +417,11 @@ The filter bar combines two dimensions in a single `VocabularyListFilter`: a liv ```ts interface VocabularyListFilter { freeText: string; // cross-field substring over id/enword/cnword (legacy) - root: IFilterDefinition; // actslib condition definition (the dialog's result) + root: FilterRoot; // the dialog's result: definition, or bare condition (case 1) } -// IFilterDefinition (actslib): { join?: 'AND'|'OR'; conditions: (IFilterCondition | IFilterDefinition)[] } +// FilterRoot (actslib) = IFilterCondition | IFilterDefinition +// IFilterDefinition: { join?: 'AND'|'OR'; conditions: (IFilterCondition | IFilterDefinition)[] } // IFilterCondition: { property; operation: FilterOperation; lowValue?; highValue?; enumValues? } ``` @@ -542,14 +543,15 @@ interface VocabularySelectOption { ### 10.5 Filter models ```ts -// actslib (shared filter dialog contract): IFilterDefinition / IFilterCondition / -// FilterOperation / FilterJoinType — the vocabulary filter IS a definition now. +// actslib (shared filter dialog contract): FilterRoot (= IFilterCondition | +// IFilterDefinition) / IFilterCondition / FilterOperation / FilterJoinType — +// the vocabulary filter IS an actslib root now (bare condition for case 1). // vocabulary.ts schema: interface FilterableProperty { key; labelKey; kind: 'string'|'number'|'date'|'enum'; operations?; enumValues?; choices?; customOperators?; numberRange?; prepareValue?; } const VOCABULARY_FILTER_PROPERTIES: FilterableProperty[]; // enword, cnword, rating const VOCABULARY_IS_PHRASE: FilterCustomOperator; // Contains ' ' emit/recognize -interface VocabularyListFilter { freeText: string; root: IFilterDefinition; } +interface VocabularyListFilter { freeText: string; root: FilterRoot; } ``` `RatingOperatorEnum` (`Equals`, `GreaterThan`, `LessThan`, `LargerOrEquals`, `LessOrEquals`), `SelectionModeEnum` (`ByID`, `FreeSelection`, `ByCount`), `RatingCondition` and `summarizeRatingFilter()` live in `ui-common.ts` — they are shared by the vocabulary, knowledge, Chinese and translate list filters — alongside `matchRating()`, which compares an unrated (`0`) word numerically (the vocabulary page evaluates its rating conditions via actslib `FilterUtility` with the same semantics). diff --git a/package-lock.json b/package-lock.json index 66edc7f..3f8340c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@angular/platform-browser": "22.1.4", "@angular/router": "22.1.4", "@jsverse/transloco": "^8.2.1", - "actslib": "^0.6.81", + "actslib": "^0.6.83", "angular-auth-oidc-client": "^22.0.0", "date-fns": "^4.1.0", "dompurify": "^3.4.11", @@ -5579,9 +5579,9 @@ } }, "node_modules/actslib": { - "version": "0.6.81", - "resolved": "https://registry.npmmirror.com/actslib/-/actslib-0.6.81.tgz", - "integrity": "sha512-vdo0E0GCtfGGZUEVoruMaY6yIiflBHTuDUsStDQKfC3QZ+NebA5UaT0SkHx4XCP5cUhNWX/0erezoAhFEIixzQ==", + "version": "0.6.83", + "resolved": "https://registry.npmmirror.com/actslib/-/actslib-0.6.83.tgz", + "integrity": "sha512-fzfCNwQUtDPvS+A9L97H8OM7dWj/cGFfJpqj+3vbj5c/idqHyxPf94dyjU0ZLlEMkHQ8Upp5ybTbLQsX4MVDwQ==", "license": "MIT", "engines": { "node": ">= 24.0" diff --git a/package.json b/package.json index b08c724..2ceedf3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "knowledgebuilder", - "version": "1.9.1", + "version": "1.9.5", "license": "MIT", "description": "An AI-powered web-based Learning app for English, Chinese, and Knowledge Bank part.", "author": { @@ -34,7 +34,7 @@ "@angular/platform-browser": "22.1.4", "@angular/router": "22.1.4", "@jsverse/transloco": "^8.2.1", - "actslib": "^0.6.81", + "actslib": "^0.6.83", "angular-auth-oidc-client": "^22.0.0", "date-fns": "^4.1.0", "dompurify": "^3.4.11", diff --git a/src/app/interfaces/knowledge-list-filter.ts b/src/app/interfaces/knowledge-list-filter.ts index ff3f930..423033c 100644 --- a/src/app/interfaces/knowledge-list-filter.ts +++ b/src/app/interfaces/knowledge-list-filter.ts @@ -2,14 +2,14 @@ // Filter pipeline of the knowledge exercises list page. // // The filter is edited by SharedFilterDialogComponent; its seed and result are -// actslib `IFilterDefinition`, so this page keeps no tree model of its own +// actslib `FilterRoot` (a group definition, or a bare condition for a +// single-condition filter), so this page keeps no tree model of its own // (see docs/reusable-filter-dialog-design.md — this page is the enum proof). import { - FilterJoinType, FilterOperation, FilterUtility, - type IFilterDefinition, + type FilterRoot, } from 'actslib'; import { hasActiveFilterDefinition } from "../shared/filter-dialog/filter-dialog-model"; @@ -90,12 +90,6 @@ export const KNOWLEDGE_FILTER_PROPERTIES: FilterableProperty[] = [ }, ]; -/** A fresh (empty) filter definition: matches everything, shown as "new filter". */ -export const emptyKnowledgeFilterDefinition = (): IFilterDefinition => ({ - join: FilterJoinType.AND, - conditions: [], -}); - /** * Combined criteria of the knowledge list filter bar. `freeText` applies live * (multi-term cross-field substring over id/question/tags/options/answers/ @@ -108,8 +102,9 @@ export const emptyKnowledgeFilterDefinition = (): IFilterDefinition => ({ export interface KnowledgeListFilter { /** Cross-field substring search (every term must match somewhere). */ freeText: string; - /** actslib filter definition (text/enum/rating leaves and nested groups). */ - root: IFilterDefinition; + /** actslib filter root (text/enum/rating leaves and nested groups; a bare + * condition when the filter is a single condition). */ + root: FilterRoot; } /** diff --git a/src/app/interfaces/learnchinese.ts b/src/app/interfaces/learnchinese.ts index b8fbbd7..892fcd3 100644 --- a/src/app/interfaces/learnchinese.ts +++ b/src/app/interfaces/learnchinese.ts @@ -2,10 +2,9 @@ // Data file. import { - FilterJoinType, FilterOperation, FilterUtility, - type IFilterDefinition, + type FilterRoot, } from 'actslib'; import { hasActiveFilterDefinition } from "../shared/filter-dialog/filter-dialog-model"; @@ -17,8 +16,9 @@ import type { KnowledgeExerciseFileContent } from "./questionbank-base"; // ── Filter-bar schema (shared filter dialog) ─────────────────────────────── // // The Chinese list filter is edited by SharedFilterDialogComponent; its seed -// and result are actslib `IFilterDefinition`, so this page keeps no tree model -// of its own (see docs/reusable-filter-dialog-design.md). +// and result are actslib `FilterRoot` (a group definition, or a bare condition +// for a single-condition filter), so this page keeps no tree model of its own +// (see docs/reusable-filter-dialog-design.md). /** actslib string comparisons are case-sensitive while the page matches * case-insensitively — the dialog folds emitted text values (this hook) and @@ -77,12 +77,6 @@ export const CHINESE_FILTER_PROPERTIES: FilterableProperty[] = [ }, ]; -/** A fresh (empty) filter definition: matches everything, shown as "new filter". */ -export const emptyChineseFilterDefinition = (): IFilterDefinition => ({ - join: FilterJoinType.AND, - conditions: [], -}); - /** * Combined criteria of the Chinese list filter bar. `freeText` applies live * (cross-field substring over id/subject/author/content/source); `root` is @@ -93,8 +87,9 @@ export const emptyChineseFilterDefinition = (): IFilterDefinition => ({ export interface ChineseListFilter { /** Cross-field substring search (legacy default-predicate behaviour). */ freeText: string; - /** actslib filter definition (text/rating leaves and nested AND/OR groups). */ - root: IFilterDefinition; + /** actslib filter root (text/rating leaves and nested AND/OR groups; a bare + * condition when the filter is a single condition). */ + root: FilterRoot; } /** diff --git a/src/app/interfaces/sentence.ts b/src/app/interfaces/sentence.ts index 99f1238..5acf78f 100644 --- a/src/app/interfaces/sentence.ts +++ b/src/app/interfaces/sentence.ts @@ -1,9 +1,8 @@ import { - FilterJoinType, FilterOperation, FilterUtility, FisherYatesShuffle, - type IFilterDefinition, + type FilterRoot, } from 'actslib'; import { hasActiveFilterDefinition } from "../shared/filter-dialog/filter-dialog-model"; @@ -13,8 +12,9 @@ import { pickWeighted } from "../shared/utils/shuffle"; // ── Filter-bar schema (shared filter dialog) ─────────────────────────────── // // The sentence list filter is edited by SharedFilterDialogComponent; its seed -// and result are actslib `IFilterDefinition`, so this page keeps no tree model -// of its own (see docs/reusable-filter-dialog-design.md). +// and result are actslib `FilterRoot` (a group definition, or a bare condition +// for a single-condition filter), so this page keeps no tree model of its own +// (see docs/reusable-filter-dialog-design.md). /** actslib string comparisons are case-sensitive while the page matches * case-insensitively — the dialog folds emitted text values (this hook) and @@ -66,12 +66,6 @@ export const SENTENCE_FILTER_PROPERTIES: FilterableProperty[] = [ }, ]; -/** A fresh (empty) filter definition: matches everything, shown as "new filter". */ -export const emptySentenceFilterDefinition = (): IFilterDefinition => ({ - join: FilterJoinType.AND, - conditions: [], -}); - /** * Combined criteria of the sentence list filter bar. `freeText` applies live * (cross-field substring over id/ensent/cnsent); `root` is the actslib @@ -82,8 +76,9 @@ export const emptySentenceFilterDefinition = (): IFilterDefinition => ({ export interface SentenceListFilter { /** Cross-field substring search over id/ensent/cnsent (legacy behaviour). */ freeText: string; - /** actslib filter definition (text/rating leaves and nested AND/OR groups). */ - root: IFilterDefinition; + /** actslib filter root (text/rating leaves and nested AND/OR groups; a bare + * condition when the filter is a single condition). */ + root: FilterRoot; } /** diff --git a/src/app/interfaces/vocabulary.spec.ts b/src/app/interfaces/vocabulary.spec.ts index fc83e24..ce63082 100644 --- a/src/app/interfaces/vocabulary.spec.ts +++ b/src/app/interfaces/vocabulary.spec.ts @@ -1,5 +1,4 @@ import { FilterJoinType, FilterOperation } from 'actslib'; - import type { IFilterCondition, IFilterDefinition } from 'actslib'; import { @@ -7,7 +6,6 @@ import { VOCABULARY_IS_PHRASE, VOCABULARY_UPLOAD_MAX_ITEMS, buildVocabularyQuizQuestions, - emptyVocabularyFilterDefinition, isVocabularyListFilterEmpty, matchVocabularyListFilter, parseVocabularyUpload, @@ -16,7 +14,8 @@ import { const baseFilter: VocabularyListFilter = { freeText: '', - root: emptyVocabularyFilterDefinition(), + // case 0 — the cleared filter (the shared dialog's emptyFilterDefinition shape) + root: { join: FilterJoinType.AND, conditions: [] }, }; const item = { id: 7, enword: 'Apple Pie', cnword: '苹果派' }; diff --git a/src/app/interfaces/vocabulary.ts b/src/app/interfaces/vocabulary.ts index 473dc5e..3979f48 100644 --- a/src/app/interfaces/vocabulary.ts +++ b/src/app/interfaces/vocabulary.ts @@ -1,9 +1,8 @@ import { FisherYatesShuffle, - FilterJoinType, FilterOperation, FilterUtility, - type IFilterDefinition, + type FilterRoot, } from 'actslib'; import { hasActiveFilterDefinition } from "../shared/filter-dialog/filter-dialog-model"; @@ -180,8 +179,9 @@ export interface ReviewQueueItem { // ── Filter-bar schema (shared filter dialog) ───────────────────────────────── // // The vocabulary filter is edited by SharedFilterDialogComponent; its seed and -// result are actslib `IFilterDefinition`, so this page keeps no tree model of -// its own (see docs/reusable-filter-dialog-design.md). +// result are actslib `FilterRoot` (a group definition, or a bare condition for +// a single-condition filter), so this page keeps no tree model of its own +// (see docs/reusable-filter-dialog-design.md). /** The `isPhrase` custom operator: a phrase is English text containing a * space. Valueless (the editor shows no input) and recognized from its own @@ -245,12 +245,6 @@ export const VOCABULARY_FILTER_PROPERTIES: FilterableProperty[] = [ }, ]; -/** A fresh (empty) filter definition: matches everything, shown as "new filter". */ -export const emptyVocabularyFilterDefinition = (): IFilterDefinition => ({ - join: FilterJoinType.AND, - conditions: [], -}); - /** * Combined criteria of the vocabulary list filter bar. `freeText` applies live * (cross-field substring over id/enword/cnword); `root` is the actslib @@ -261,8 +255,9 @@ export const emptyVocabularyFilterDefinition = (): IFilterDefinition => ({ export interface VocabularyListFilter { /** Cross-field substring search over id/enword/cnword (legacy behaviour). */ freeText: string; - /** actslib filter definition (word/rating leaves and nested AND/OR groups). */ - root: IFilterDefinition; + /** actslib filter root (word/rating leaves and nested AND/OR groups; a bare + * condition when the filter is a single condition). */ + root: FilterRoot; } /** diff --git a/src/app/pages/chinese-exercises/chinese-exercises-list.component.ts b/src/app/pages/chinese-exercises/chinese-exercises-list.component.ts index e26cfb2..d377d85 100644 --- a/src/app/pages/chinese-exercises/chinese-exercises-list.component.ts +++ b/src/app/pages/chinese-exercises/chinese-exercises-list.component.ts @@ -32,7 +32,7 @@ import { MatTableModule } from '@angular/material/table'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { TranslocoModule, TranslocoService } from '@jsverse/transloco'; -import type { IFilterDefinition } from 'actslib'; +import type { FilterRoot } from 'actslib'; import type { LearnChineseFileItem, LearningContent } from '../../interfaces'; import { CHINESE_FILTER_PROPERTIES, getChineseReciteItemDisplayContent } from '../../interfaces'; @@ -89,7 +89,7 @@ export class ChineseExercisesListComponent implements OnInit { // filter dialog); this child only renders it for the menu label and // forwards intents. freeText is a local field for the input box, emitted // live. - readonly filterDefinition = input.required(); + readonly filterDefinition = input.required(); /** * Free text currently applied by the container. The container's filter * survives the destroy/recreate of this screen, so it seeds the input box diff --git a/src/app/pages/chinese-exercises/chinese-exercises.component.ts b/src/app/pages/chinese-exercises/chinese-exercises.component.ts index 12e62ff..ea01808 100644 --- a/src/app/pages/chinese-exercises/chinese-exercises.component.ts +++ b/src/app/pages/chinese-exercises/chinese-exercises.component.ts @@ -13,7 +13,7 @@ import { MatDialog } from '@angular/material/dialog'; import type { MatSelectChange } from '@angular/material/select'; import { MatTableDataSource } from '@angular/material/table'; import { Router } from '@angular/router'; -import { FisherYatesShuffle, type IFilterDefinition } from 'actslib'; +import { FisherYatesShuffle, type FilterRoot } from 'actslib'; import type { ChineseListFilter, @@ -31,17 +31,13 @@ import { QuestionBankTypeEnum, SelectionModeEnum, convertChineseReciteItemToKnowledge, - emptyChineseFilterDefinition, getQuestionBankLevelName, isChineseListFilterEmpty, matchChineseListFilter, } from '../../interfaces'; import { LearningContentService, LearningRatingService, UIService } from '../../services'; -import type { - FilterDialogData, - FilterDialogResult, -} from '../../shared/filter-dialog/filter-dialog-model'; -import { SharedFilterDialogComponent } from '../../shared/filter-dialog/filter-dialog.component'; +import { openFilterDialog } from '../../shared/filter-dialog/filter-dialog-launcher'; +import { emptyFilterDefinition } from '../../shared/filter-dialog/filter-dialog-model'; import { FooterComponent } from '../../shared/footer/footer'; import { AppPageTitle } from '../page-title/page-title'; @@ -75,7 +71,7 @@ export class ChineseExercisesComponent implements OnInit { // condition definition (it opens the shared filter dialog). freeText is fed // back from the child's live input via freeTextChanged. freeText = signal(''); - filterDefinition = signal(emptyChineseFilterDefinition()); + filterDefinition = signal(emptyFilterDefinition()); // Parsed form of the active list filter; applyListFilter keeps it in sync // with dataSource.filter so the row predicate does not JSON.parse per row. // Null means no filter (MatTable skips the predicate for an empty filter). @@ -208,34 +204,19 @@ export class ChineseExercisesComponent implements OnInit { } onDefineFilter(): void { - this.dialog - .open( - SharedFilterDialogComponent, - { - data: { - properties: CHINESE_FILTER_PROPERTIES, - root: this.filterDefinition(), - }, - // Tree navigator + detail pane sit side by side; the splitter can - // resize them but needs the extra width to start from. - width: '880px', - enterAnimationDuration: 800, - exitAnimationDuration: 500, - } - ) - .afterClosed() - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(result => { - // undefined (Cancel / backdrop / Esc) leaves the previous filter untouched. - if (result !== undefined) { - this.filterDefinition.set(result.root); - this.applyCurrentFilter(); - } - }); + openFilterDialog(this.dialog, { + destroyRef: this.destroyRef, + properties: CHINESE_FILTER_PROPERTIES, + current: this.filterDefinition(), + onApplied: root => { + this.filterDefinition.set(root); + this.applyCurrentFilter(); + }, + }); } onClearFilter(): void { - this.filterDefinition.set(emptyChineseFilterDefinition()); + this.filterDefinition.set(emptyFilterDefinition()); this.applyCurrentFilter(); } diff --git a/src/app/pages/knowledge-exercises/knowledge-exercises-list/knowledge-exercises-list.component.ts b/src/app/pages/knowledge-exercises/knowledge-exercises-list/knowledge-exercises-list.component.ts index 6536fc6..0db0e97 100644 --- a/src/app/pages/knowledge-exercises/knowledge-exercises-list/knowledge-exercises-list.component.ts +++ b/src/app/pages/knowledge-exercises/knowledge-exercises-list/knowledge-exercises-list.component.ts @@ -32,7 +32,7 @@ import { MatTableModule } from '@angular/material/table'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { TranslocoModule, TranslocoService } from '@jsverse/transloco'; -import type { IFilterDefinition } from 'actslib'; +import type { FilterRoot } from 'actslib'; import type { KnowledgeExerciseFileContent, LearningContent } from '../../../interfaces'; import { KNOWLEDGE_FILTER_PROPERTIES } from '../../../interfaces'; @@ -101,7 +101,7 @@ export class KnowledgeExercisesListComponent implements OnInit { // filter dialog); this child only renders it for the menu label and // forwards intents. freeText is a local field for the input box, emitted // live. - readonly filterDefinition = input.required(); + readonly filterDefinition = input.required(); /** * Free text currently applied by the container, seeded into the input box * on init. The list screen stays mounted across detail visits (the container diff --git a/src/app/pages/knowledge-exercises/knowledge-exercises-list/knowledge-exercises.component.ts b/src/app/pages/knowledge-exercises/knowledge-exercises-list/knowledge-exercises.component.ts index a475428..247e5d4 100644 --- a/src/app/pages/knowledge-exercises/knowledge-exercises-list/knowledge-exercises.component.ts +++ b/src/app/pages/knowledge-exercises/knowledge-exercises-list/knowledge-exercises.component.ts @@ -19,7 +19,7 @@ import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { Router } from '@angular/router'; import { TranslocoModule } from '@jsverse/transloco'; -import { FisherYatesShuffle, type IFilterDefinition } from 'actslib'; +import { FisherYatesShuffle, type FilterRoot } from 'actslib'; import type { KnowledgeExerciseFileContent, @@ -36,7 +36,6 @@ import { SelectionModeEnum, convertQuestionBankItemToMarkdown, convertToQuestionBankItem, - emptyKnowledgeFilterDefinition, isKnowledgeListFilterEmpty, matchKnowledgeListFilter, } from '../../../interfaces'; @@ -46,11 +45,8 @@ import { UIService, ratingItemKey, } from '../../../services'; -import type { - FilterDialogData, - FilterDialogResult, -} from '../../../shared/filter-dialog/filter-dialog-model'; -import { SharedFilterDialogComponent } from '../../../shared/filter-dialog/filter-dialog.component'; +import { openFilterDialog } from '../../../shared/filter-dialog/filter-dialog-launcher'; +import { emptyFilterDefinition } from '../../../shared/filter-dialog/filter-dialog-model'; import { FooterComponent } from '../../../shared/footer/footer'; import { MarkdownContentComponent } from '../../../shared/markdown-content'; import { AppPageTitle } from '../../page-title/page-title'; @@ -104,7 +100,7 @@ export class KnowledgeExercisesComponent implements OnInit { // condition definition (it opens the shared filter dialog). freeText is fed // back from the child's live input via freeTextChanged. freeText = signal(''); - filterDefinition = signal(emptyKnowledgeFilterDefinition()); + filterDefinition = signal(emptyFilterDefinition()); // Parsed form of the active list filter; applyListFilter keeps it in sync // with dataSource.filter so the row predicate does not JSON.parse per row. // Null means no filter (MatTable skips the predicate for an empty filter). @@ -253,34 +249,19 @@ export class KnowledgeExercisesComponent implements OnInit { } onDefineFilter(): void { - this.dialog - .open( - SharedFilterDialogComponent, - { - data: { - properties: KNOWLEDGE_FILTER_PROPERTIES, - root: this.filterDefinition(), - }, - // Tree navigator + detail pane sit side by side; the splitter can - // resize them but needs the extra width to start from. - width: '880px', - enterAnimationDuration: 800, - exitAnimationDuration: 500, - } - ) - .afterClosed() - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(result => { - // undefined (Cancel / backdrop / Esc) leaves the previous filter untouched. - if (result !== undefined) { - this.filterDefinition.set(result.root); - this.applyCurrentFilter(); - } - }); + openFilterDialog(this.dialog, { + destroyRef: this.destroyRef, + properties: KNOWLEDGE_FILTER_PROPERTIES, + current: this.filterDefinition(), + onApplied: root => { + this.filterDefinition.set(root); + this.applyCurrentFilter(); + }, + }); } onClearFilter(): void { - this.filterDefinition.set(emptyKnowledgeFilterDefinition()); + this.filterDefinition.set(emptyFilterDefinition()); this.applyCurrentFilter(); } diff --git a/src/app/pages/translate-exercises/translate-exercises-sentence-list.component.ts b/src/app/pages/translate-exercises/translate-exercises-sentence-list.component.ts index 86c2ce1..f51d6dc 100644 --- a/src/app/pages/translate-exercises/translate-exercises-sentence-list.component.ts +++ b/src/app/pages/translate-exercises/translate-exercises-sentence-list.component.ts @@ -32,7 +32,7 @@ import { MatTableModule } from '@angular/material/table'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { TranslocoModule, TranslocoService } from '@jsverse/transloco'; -import type { IFilterDefinition } from 'actslib'; +import type { FilterRoot } from 'actslib'; import type { LearnEnglishSentFileItem, LearningContent } from '../../interfaces'; import { SENTENCE_FILTER_PROPERTIES } from '../../interfaces'; @@ -90,7 +90,7 @@ export class TranslateExercisesSentenceListComponent implements OnInit { // filter dialog); this child only renders it for the menu label and // forwards intents. freeText is a local field for the input box, emitted // live. - readonly filterDefinition = input.required(); + readonly filterDefinition = input.required(); /** * Free text currently applied by the container. The container's filter * survives the @switch destroy/recreate of this screen (e.g. returning from diff --git a/src/app/pages/translate-exercises/translate-exercises.component.ts b/src/app/pages/translate-exercises/translate-exercises.component.ts index eaca0de..fea7c18 100644 --- a/src/app/pages/translate-exercises/translate-exercises.component.ts +++ b/src/app/pages/translate-exercises/translate-exercises.component.ts @@ -14,7 +14,7 @@ import { MatDialog } from '@angular/material/dialog'; import type { MatSelectChange } from '@angular/material/select'; import { MatTableDataSource } from '@angular/material/table'; import { Router } from '@angular/router'; -import { FisherYatesShuffle, type IFilterDefinition } from 'actslib'; +import { FisherYatesShuffle, type FilterRoot } from 'actslib'; import type { KnowledgeExerciseFileContent, @@ -36,7 +36,6 @@ import { SelectionModeEnum, TranslateDirectionEnum, buildSentenceQuizQuestions, - emptySentenceFilterDefinition, isSentenceListFilterEmpty, matchSentenceListFilter, } from '../../interfaces'; @@ -48,11 +47,8 @@ import { UIService, ratingItemKey, } from '../../services'; -import type { - FilterDialogData, - FilterDialogResult, -} from '../../shared/filter-dialog/filter-dialog-model'; -import { SharedFilterDialogComponent } from '../../shared/filter-dialog/filter-dialog.component'; +import { openFilterDialog } from '../../shared/filter-dialog/filter-dialog-launcher'; +import { emptyFilterDefinition } from '../../shared/filter-dialog/filter-dialog-model'; import { FooterComponent } from '../../shared/footer/footer'; import { AppPageTitle } from '../page-title/page-title'; @@ -118,7 +114,7 @@ export class TranslateExercisesComponent implements OnInit { // condition definition (it opens the shared filter dialog). freeText is fed // back from the child's live input via freeTextChanged. freeText = signal(''); - filterDefinition = signal(emptySentenceFilterDefinition()); + filterDefinition = signal(emptyFilterDefinition()); // Parsed form of the active list filter; applyListFilter keeps it in sync // with dataSource.filter so the row predicate does not JSON.parse per row. // Null means no filter (MatTable skips the predicate for an empty filter). @@ -287,34 +283,19 @@ export class TranslateExercisesComponent implements OnInit { } onDefineFilter(): void { - this.dialog - .open( - SharedFilterDialogComponent, - { - data: { - properties: SENTENCE_FILTER_PROPERTIES, - root: this.filterDefinition(), - }, - // Tree navigator + detail pane sit side by side; the splitter can - // resize them but needs the extra width to start from. - width: '880px', - enterAnimationDuration: 800, - exitAnimationDuration: 500, - } - ) - .afterClosed() - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(result => { - // undefined (Cancel / backdrop / Esc) leaves the previous filter untouched. - if (result !== undefined) { - this.filterDefinition.set(result.root); - this.applyCurrentFilter(); - } - }); + openFilterDialog(this.dialog, { + destroyRef: this.destroyRef, + properties: SENTENCE_FILTER_PROPERTIES, + current: this.filterDefinition(), + onApplied: root => { + this.filterDefinition.set(root); + this.applyCurrentFilter(); + }, + }); } onClearFilter(): void { - this.filterDefinition.set(emptySentenceFilterDefinition()); + this.filterDefinition.set(emptyFilterDefinition()); this.applyCurrentFilter(); } diff --git a/src/app/pages/vocabulary-exercises/vocabulary-exercises-word-list.component.ts b/src/app/pages/vocabulary-exercises/vocabulary-exercises-word-list.component.ts index 1937cd7..124f34e 100644 --- a/src/app/pages/vocabulary-exercises/vocabulary-exercises-word-list.component.ts +++ b/src/app/pages/vocabulary-exercises/vocabulary-exercises-word-list.component.ts @@ -32,7 +32,7 @@ import { MatTableModule } from '@angular/material/table'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { TranslocoModule, TranslocoService } from '@jsverse/transloco'; -import type { IFilterDefinition } from 'actslib'; +import type { FilterRoot } from 'actslib'; import type { LearnEnglishWordFileItem, LearningContent } from '../../interfaces'; import { VOCABULARY_FILTER_PROPERTIES } from '../../interfaces'; @@ -113,7 +113,7 @@ export class VocabularyExercisesWordListComponent implements OnInit { // The container owns the applied condition definition (it opens the shared // filter dialog); this child only renders it for the menu labels and forwards // intents. freeText is a local field for the input box, emitted live. - readonly filterDefinition = input.required(); + readonly filterDefinition = input.required(); /** * Free text currently applied by the container. The container's filter * survives the @switch destroy/recreate of this screen (e.g. returning from diff --git a/src/app/pages/vocabulary-exercises/vocabulary-exercises.component.spec.ts b/src/app/pages/vocabulary-exercises/vocabulary-exercises.component.spec.ts index 18bb845..c58b5b3 100644 --- a/src/app/pages/vocabulary-exercises/vocabulary-exercises.component.spec.ts +++ b/src/app/pages/vocabulary-exercises/vocabulary-exercises.component.spec.ts @@ -333,12 +333,27 @@ describe('VocabularyExercisesComponent', () => { component.onDefineFilter(); expect(component.dataSource.filter).toContain('BeginsWith'); expect(component.dataSource.filter).toContain('"lowValue":"hello"'); - expect(component.filterDefinition().conditions).toEqual([ + // A two-member result stays a definition (Simplify only touches case 1). + const applied = component.filterDefinition() as IFilterDefinition; + expect(applied.conditions).toEqual([ cond('enword', FilterOperation.BeginsWith, 'hello'), cond('rating', FilterOperation.Equal, 4), ]); }); + it('onDefineFilter applies a single-condition (case-1) result and filters the rows', () => { + component.dataSource.data = mockWordContent; + mockDialog.open.mockReturnValue({ + afterClosed: () => of({ root: cond('enword', FilterOperation.BeginsWith, 'hello') }), + }); + component.onDefineFilter(); + // The bare condition is stored as-is; MatchFilter evaluates it per row. + expect(component.filterDefinition()).toEqual( + cond('enword', FilterOperation.BeginsWith, 'hello') + ); + expect(component.dataSource.filteredData.map(w => w.enword)).toEqual(['hello']); + }); + it('onDefineFilter with a cancelled dialog leaves the filter untouched', () => { const tree = andG(cond('cnword', FilterOperation.Contains, 'x')); component.filterDefinition.set(tree); diff --git a/src/app/pages/vocabulary-exercises/vocabulary-exercises.component.ts b/src/app/pages/vocabulary-exercises/vocabulary-exercises.component.ts index ec32ee9..3897546 100644 --- a/src/app/pages/vocabulary-exercises/vocabulary-exercises.component.ts +++ b/src/app/pages/vocabulary-exercises/vocabulary-exercises.component.ts @@ -16,7 +16,7 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { MatTableDataSource } from '@angular/material/table'; import { Router } from '@angular/router'; import { TranslocoService } from '@jsverse/transloco'; -import { FisherYatesShuffle, type IFilterDefinition } from 'actslib'; +import { FisherYatesShuffle, type FilterRoot } from 'actslib'; import type { LearnEnglishWordFileItem, @@ -41,14 +41,13 @@ import { VOCABULARY_FILTER_PROPERTIES, VOCABULARY_UPLOAD_MAX_ITEMS, buildVocabularyQuizQuestions, - emptyVocabularyFilterDefinition, isVocabularyListFilterEmpty, matchVocabularyListFilter, parseVocabularyUpload, } from '../../interfaces'; import { LearningContentService, LearningRatingService, UIService } from '../../services'; -import type { FilterDialogData, FilterDialogResult } from '../../shared/filter-dialog/filter-dialog-model'; -import { SharedFilterDialogComponent } from '../../shared/filter-dialog/filter-dialog.component'; +import { openFilterDialog } from '../../shared/filter-dialog/filter-dialog-launcher'; +import { emptyFilterDefinition } from '../../shared/filter-dialog/filter-dialog-model'; import { FooterComponent } from '../../shared/footer/footer'; import { AppPageTitle } from '../page-title/page-title'; @@ -121,7 +120,7 @@ export class VocabularyExercisesComponent implements OnInit { // condition definition (it opens the shared filter dialog). freeText is fed // back from the child's live input via freeTextChanged. freeText = signal(''); - filterDefinition = signal(emptyVocabularyFilterDefinition()); + filterDefinition = signal(emptyFilterDefinition()); // Parsed form of the active list filter; applyListFilter keeps it in sync // with dataSource.filter so the row predicate does not JSON.parse per row. // Null means no filter (MatTable skips the predicate for an empty filter). @@ -318,34 +317,19 @@ export class VocabularyExercisesComponent implements OnInit { } onDefineFilter(): void { - this.dialog - .open( - SharedFilterDialogComponent, - { - data: { - properties: VOCABULARY_FILTER_PROPERTIES, - root: this.filterDefinition(), - }, - // Tree navigator + detail pane sit side by side; the splitter can - // resize them but needs the extra width to start from. - width: '880px', - enterAnimationDuration: 800, - exitAnimationDuration: 500, - } - ) - .afterClosed() - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(result => { - // undefined (Cancel / backdrop / Esc) leaves the previous filter untouched. - if (result !== undefined) { - this.filterDefinition.set(result.root); - this.applyCurrentFilter(); - } - }); + openFilterDialog(this.dialog, { + destroyRef: this.destroyRef, + properties: VOCABULARY_FILTER_PROPERTIES, + current: this.filterDefinition(), + onApplied: root => { + this.filterDefinition.set(root); + this.applyCurrentFilter(); + }, + }); } onClearFilter(): void { - this.filterDefinition.set(emptyVocabularyFilterDefinition()); + this.filterDefinition.set(emptyFilterDefinition()); this.applyCurrentFilter(); } diff --git a/src/app/shared/filter-dialog/filter-dialog-launcher.spec.ts b/src/app/shared/filter-dialog/filter-dialog-launcher.spec.ts new file mode 100644 index 0000000..8ddba2e --- /dev/null +++ b/src/app/shared/filter-dialog/filter-dialog-launcher.spec.ts @@ -0,0 +1,88 @@ +import type { DestroyRef } from '@angular/core'; +import type { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { FilterJoinType, FilterOperation } from 'actslib'; +import type { FilterRoot, IFilterCondition } from 'actslib'; +import { of } from 'rxjs'; +import { vi } from 'vitest'; + +import { + FILTER_DIALOG_CONFIG, + openFilterDialog, +} from './filter-dialog-launcher'; +import type { FilterDialogResult, FilterableProperty } from './filter-dialog-model'; +import { SharedFilterDialogComponent } from './filter-dialog.component'; + +const SCHEMA: FilterableProperty[] = [ + { key: 'enword', labelKey: 'test.word', kind: 'string' }, +]; + +const bare: IFilterCondition = { + property: 'enword', + operation: FilterOperation.Contains, + lowValue: 'app', +}; + +describe('openFilterDialog', () => { + let dialog: MatDialog; + let openSpy: ReturnType; + let emitResult: FilterDialogResult | undefined; + let destroyRef: DestroyRef; + + beforeEach(() => { + openSpy = vi.fn(() => ({ + afterClosed: () => of(emitResult), + }) as unknown as MatDialogRef); + dialog = { open: openSpy } as unknown as MatDialog; + // The launcher only ever REGISTERS the teardown (takeUntilDestroyed); the + // spec never destroys, so a no-op onDestroy is faithful. + destroyRef = { onDestroy: vi.fn() } as unknown as DestroyRef; + }); + + const launch = (onApplied = vi.fn()): ReturnType => { + openFilterDialog(dialog, { + destroyRef, + properties: SCHEMA, + current: bare, + onApplied, + }); + return onApplied; + }; + + it('opens the shared dialog with the seeded root and the standard config', () => { + launch(); + expect(openSpy).toHaveBeenCalledTimes(1); + expect(openSpy.mock.calls[0][0]).toBe(SharedFilterDialogComponent); + const config = openSpy.mock.calls[0][1] as { data: { properties: FilterableProperty[]; root: FilterRoot } }; + expect(config.data.properties).toBe(SCHEMA); + expect(config.data.root).toEqual(bare); + expect(config).toEqual(expect.objectContaining(FILTER_DIALOG_CONFIG)); + }); + + it('passes the optional maxDepth/titleKey overrides through', () => { + openFilterDialog(dialog, { + destroyRef, + properties: SCHEMA, + current: undefined, + maxDepth: 2, + titleKey: 'test.title', + onApplied: vi.fn(), + }); + const config = openSpy.mock.calls[0][1] as { data: { maxDepth?: number; titleKey?: string; root?: FilterRoot } }; + expect(config.data.maxDepth).toBe(2); + expect(config.data.titleKey).toBe('test.title'); + expect(config.data.root).toBeUndefined(); + }); + + it('runs onApplied with the submitted root (Submit)', () => { + emitResult = { root: { join: FilterJoinType.AND, conditions: [bare] } }; + const onApplied = launch(); + expect(onApplied).toHaveBeenCalledTimes(1); + expect(onApplied).toHaveBeenCalledWith(emitResult.root); + }); + + it('leaves the previous filter untouched on Cancel (undefined result)', () => { + emitResult = undefined; + const onApplied = launch(); + expect(onApplied).not.toHaveBeenCalled(); + }); +}); diff --git a/src/app/shared/filter-dialog/filter-dialog-launcher.ts b/src/app/shared/filter-dialog/filter-dialog-launcher.ts new file mode 100644 index 0000000..3805b95 --- /dev/null +++ b/src/app/shared/filter-dialog/filter-dialog-launcher.ts @@ -0,0 +1,69 @@ +// The one-call entry point list pages use to open the shared filter dialog +// (docs/reusable-filter-dialog-design.md §10). The sizing/animation config, +// the seed wiring and the Cancel guard are the whole open contract — they +// were byte-identical in every page that grew them locally (review M4), so +// they live here once. + +import type { DestroyRef } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import type { MatDialog, MatDialogConfig } from '@angular/material/dialog'; +import type { FilterRoot } from 'actslib'; + +import type { FilterDialogData, FilterDialogResult, FilterableProperty } from './filter-dialog-model'; +import { SharedFilterDialogComponent } from './filter-dialog.component'; + +/** + * Standard dialog config for the filter editor: the tree navigator + detail + * pane sit side by side; the splitter can resize them but needs the extra + * width to start from. The cap keeps the dialog inside smaller viewports. + */ +export const FILTER_DIALOG_CONFIG: Pick< + MatDialogConfig, + 'width' | 'maxWidth' | 'enterAnimationDuration' | 'exitAnimationDuration' +> = { + width: '800px', + maxWidth: '96vw', + enterAnimationDuration: 800, + exitAnimationDuration: 500, +}; + +/** Everything `openFilterDialog` needs: the page's schema, the seed, the + * optional dialog overrides, and what to do with an applied filter. */ +export interface FilterDialogLaunch { + /** the caller's injection context, for the `afterClosed` subscription */ + destroyRef: DestroyRef; + properties: FilterableProperty[]; + /** the filter in effect (any `FilterRoot` spelling; undefined opens empty) */ + current: FilterRoot | undefined; + maxDepth?: number; + titleKey?: string; + /** called only with a SUBMIT result — the dialog emits cases 1/2 */ + onApplied: (root: FilterRoot) => void; +} + +/** + * Open the shared filter dialog seeded with the filter in effect and run + * `onApplied` on the Submit result. Cancel / backdrop / Esc yield + * `undefined` and leave the previous filter untouched; case 0 cannot come + * out of the dialog (clearing is the pages' Clear Filter button). + */ +export function openFilterDialog(dialog: MatDialog, launch: FilterDialogLaunch): void { + const data: FilterDialogData = { + properties: launch.properties, + root: launch.current, + maxDepth: launch.maxDepth, + titleKey: launch.titleKey, + }; + dialog + .open( + SharedFilterDialogComponent, + { data, ...FILTER_DIALOG_CONFIG } + ) + .afterClosed() + .pipe(takeUntilDestroyed(launch.destroyRef)) + .subscribe(result => { + if (result !== undefined) { + launch.onApplied(result.root); + } + }); +} diff --git a/src/app/shared/filter-dialog/filter-dialog-model.spec.ts b/src/app/shared/filter-dialog/filter-dialog-model.spec.ts index 70a5eac..4d9094f 100644 --- a/src/app/shared/filter-dialog/filter-dialog-model.spec.ts +++ b/src/app/shared/filter-dialog/filter-dialog-model.spec.ts @@ -1,4 +1,4 @@ -import { FilterJoinType, FilterOperation } from 'actslib'; +import { FilterJoinType, FilterOperation, FilterUtility } from 'actslib'; import type { EnumLike, IFilterCondition, IFilterDefinition } from 'actslib'; import { @@ -168,17 +168,19 @@ describe('filter-dialog-model', () => { }); describe('seedTree', () => { - it('seeds an empty/undefined definition into an empty AND root', () => { + it('seeds an empty/undefined definition into an empty root (inert AND scaffold join)', () => { const root = seedTree(undefined, schema, mkIdGen()); expect(root.join).toBe(FilterJoinType.AND); expect(root.members).toEqual([]); + // The wrapper's join is scaffold (top level is a single node, so it is + // never evaluated): any definition seed yields the inert AND wrapper. const explicit = seedTree({ join: FilterJoinType.OR, conditions: [] }, schema, mkIdGen()); - expect(explicit.join).toBe(FilterJoinType.OR); + expect(explicit.join).toBe(FilterJoinType.AND); expect(explicit.members).toEqual([]); }); - it('seeds flat conditions into leaves carrying property, operator and value', () => { + it('seeds a two-condition definition as ONE root group of leaves', () => { const root = seedTree( { join: FilterJoinType.AND, @@ -190,8 +192,12 @@ describe('filter-dialog-model', () => { schema, mkIdGen() ); - expect(root.members.length).toBe(2); - const [word, num] = root.members as SharedFilterDialogLeaf[]; + // Case 2: the top level holds a single node — the definition becomes + // the root GROUP, its conditions its members. + expect(root.members.length).toBe(1); + const top = root.members[0] as SharedFilterDialogNode; + expect(top.join).toBe(FilterJoinType.AND); + const [word, num] = top.members as SharedFilterDialogLeaf[]; expect(word).toEqual(expect.objectContaining({ propertyKey: 'enword', operator: 'BeginsWith', single: 'App' })); expect(num).toEqual(expect.objectContaining({ propertyKey: 'rating', operator: '<', single: 4 })); }); @@ -217,7 +223,10 @@ describe('filter-dialog-model', () => { ], }; const root = seedTree(def, schema, mkIdGen()); - const orGroup = root.members[1] as SharedFilterDialogNode; + // Normalized to one top node: the definition IS the root group. + const top = root.members[0] as SharedFilterDialogNode; + expect(root.members.length).toBe(1); + const orGroup = top.members[1] as SharedFilterDialogNode; expect(orGroup.join).toBe(FilterJoinType.OR); const andGroup = orGroup.members[1] as SharedFilterDialogNode; expect((andGroup.members[0] as SharedFilterDialogLeaf).single).toBe('y'); @@ -251,7 +260,10 @@ describe('filter-dialog-model', () => { schema, mkIdGen() ); - const folded = root.members[0] as SharedFilterDialogLeaf; + // Two-condition definition -> one root group; its first member folds. + const top = root.members[0] as SharedFilterDialogNode; + expect(top.members.length).toBe(2); + const folded = top.members[0] as SharedFilterDialogLeaf; expect(folded.propertyKey).toBe('color'); expect(folded.choices).toEqual(['red', 'blue']); }); @@ -290,6 +302,30 @@ describe('filter-dialog-model', () => { expect(leaf.between).toEqual([2, 4]); }); + it('seeds a bare condition (case 1) into the same root + leaf as its wrapper', () => { + const bare: IFilterCondition = { + property: 'enword', + operation: FilterOperation.Contains, + lowValue: 'apple', + }; + const fromBare = seedTree(bare, schema, mkIdGen()); + const fromWrapper = seedTree( + { join: FilterJoinType.AND, conditions: [bare] }, + schema, + mkIdGen() + ); + expect(fromBare.members.length).toBe(1); + // Both spellings seed the same leaf shape (ids differ between runs). + expect(fromBare.members[0]).toEqual( + expect.objectContaining({ propertyKey: 'enword', operator: 'Contains', single: 'apple' }) + ); + expect(fromWrapper.members[0]).toEqual( + expect.objectContaining({ propertyKey: 'enword', operator: 'Contains', single: 'apple' }) + ); + // Re-emitting the bare seed yields the wrapper (Simplify at Submit restores the bare form). + expect(emitTree(fromBare, schema).conditions).toEqual([bare]); + }); + it('never mutates the caller definition', () => { const def: IFilterDefinition = { join: FilterJoinType.AND, @@ -375,7 +411,12 @@ describe('filter-dialog-model', () => { }; const first = emitTree(root, schema); const second = emitTree(seedTree(first, schema, mkIdGen()), schema); - expect(second).toEqual(first); + // Root normalization: a 2+ condition definition seeds as the scaffold's + // SINGLE top group, so the raw re-emit wraps one level deeper — and + // Simplify at the Submit boundary unwraps it back to the identical + // definition (the round-trip the dialog actually performs). + expect(second.conditions).toEqual([first]); + expect(FilterUtility.Simplify(second)).toEqual(first); }); }); @@ -466,18 +507,26 @@ describe('filter-dialog-model', () => { expect(leafIsIncomplete(ratingLeaf(0, FilterOperation.Equal), rating)).toBe(false); }); - it('nested groups need two or more members; the root is exempt', () => { + it('every rendered group must branch; only the invisible wrapper root is exempt', () => { const single: SharedFilterDialogNode = { id: 20, join: FilterJoinType.OR, members: [wordLeaf('a')] }; const empty: SharedFilterDialogNode = { id: 21, join: FilterJoinType.OR, members: [] }; expect(validateTree(rootOf(single, ratingLeaf(1)), schema).invalidGroupIds).toEqual([20]); expect(validateTree(rootOf(empty, ratingLeaf(1)), schema).invalidGroupIds).toEqual([21]); - // root with fewer than two members is fine… + // the wrapper holding one leaf is fine (case 1 — the exemption is + // structural: the wrapper is never rendered)… expect(validateTree(rootOf(wordLeaf('a')), schema).canSubmit).toBe(true); - expect(validateTree(rootOf(), schema).canSubmit).toBe(true); - // …but a deep nested violation is still caught. + // …but a violation inside any RENDERED group, the top row included, is caught. const deep: SharedFilterDialogNode = { id: 22, join: FilterJoinType.AND, members: [single, ratingLeaf(2)] }; expect(validateTree(rootOf(deep, wordLeaf('x')), schema).invalidGroupIds).toEqual([20]); }); + + it('the empty root (case 0) is not submittable — Clear Filter owns it', () => { + const v = validateTree(rootOf(), schema); + expect(v.emptyTree).toBe(true); + expect(v.canSubmit).toBe(false); + // A non-empty tree does not carry the flag. + expect(validateTree(rootOf(wordLeaf('a')), schema).emptyTree).toBe(false); + }); }); describe('summarizeFilterDefinition', () => { @@ -525,6 +574,11 @@ describe('filter-dialog-model', () => { expect(summarizeFilterDefinition(def, schema, labels)).toBe('rating 2≤x≤5'); }); + it('renders a bare condition (case 1) like its wrapper', () => { + const bare: IFilterCondition = { property: 'enword', operation: FilterOperation.Contains, lowValue: 'app' }; + expect(summarizeFilterDefinition(bare, schema, labels)).toBe('word contains app'); + }); + it('caps with an ellipsis and returns empty string for an empty definition', () => { const def: IFilterDefinition = { join: FilterJoinType.AND, @@ -535,6 +589,22 @@ describe('filter-dialog-model', () => { expect(capped.endsWith('…')).toBe(true); expect(summarizeFilterDefinition({ join: FilterJoinType.AND, conditions: [] }, schema, labels)).toBe(''); }); + + it('the ellipsis cut never splits a surrogate pair (L1)', () => { + const def: IFilterDefinition = { + join: FilterJoinType.AND, + conditions: [ + cond('enword', FilterOperation.Contains, 'abcdefghi𠀋'), // CJK Ext-B: 2 UTF-16 units + cond('enword', FilterOperation.Contains, 'klmnopqrst'), + ], + }; + // "word contains abcdefghi" is 23 units — a UTF-16 slice at 24 would + // end on the high surrogate alone; the code-point slice keeps 𠀋 whole. + const capped = summarizeFilterDefinition(def, schema, labels, 25); + expect(capped.endsWith('…')).toBe(true); + const surrogateFree = capped.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, ''); + expect(surrogateFree).not.toMatch(/[\uD800-\uDFFF]/); + }); }); describe('describeMember', () => { @@ -550,8 +620,11 @@ describe('filter-dialog-model', () => { schema, mkIdGen() ); - expect(describeMember(root.members[0], schema, labels)).toBe('word contains app'); - expect(describeMember(root.members[1], schema, labels)).toBe('group (OR)'); + // Normalized to one top group node; the labels come from its members. + const top = root.members[0] as SharedFilterDialogNode; + expect(describeMember(top, schema, labels)).toBe('group (AND)'); + expect(describeMember(top.members[0], schema, labels)).toBe('word contains app'); + expect(describeMember(top.members[1], schema, labels)).toBe('group (OR)'); }); it('labels an incomplete leaf with just its property name', () => { @@ -570,6 +643,48 @@ describe('filter-dialog-model', () => { }) ).toBe(true); }); + + it('a bare condition (case 1) is always active', () => { + expect( + hasActiveFilterDefinition({ property: 'enword', operation: FilterOperation.Contains, lowValue: 'a' }) + ).toBe(true); + }); + }); + + describe('a definition missing the conditions key (M1)', () => { + // Legal JSON decode from persistence/URL round-trips; actslib's own + // Simplify/MatchFilter treat a missing `conditions` as empty, so every + // model walk must degrade to case 0 / match-all instead of throwing. + const malformed = { join: FilterJoinType.OR } as IFilterDefinition; + + it('hasActiveFilterDefinition reads it as inactive', () => { + expect(hasActiveFilterDefinition(malformed)).toBe(false); + // Nested too: a malformed sub-group is simply no condition anywhere. + expect( + hasActiveFilterDefinition({ + join: FilterJoinType.AND, + conditions: [malformed], + }) + ).toBe(false); + }); + + it('seedTree yields no top node (the empty-seed scaffold path)', () => { + const root = seedTree(malformed, schema, mkIdGen()); + expect(root.members).toEqual([]); + // A malformed NESTED group seeds as an empty (invalid, fixable) group. + const nested = seedTree( + { join: FilterJoinType.AND, conditions: [{ property: 'enword', operation: FilterOperation.Contains, lowValue: 'x' }, malformed] }, + schema, + mkIdGen() + ); + const top = nested.members[0] as SharedFilterDialogNode; + expect(top.members.length).toBe(2); + expect((top.members[1] as SharedFilterDialogNode).members).toEqual([]); + }); + + it('summarizeFilterDefinition renders it as the empty string', () => { + expect(summarizeFilterDefinition(malformed, schema, labels)).toBe(''); + }); }); describe('findProperty', () => { diff --git a/src/app/shared/filter-dialog/filter-dialog-model.ts b/src/app/shared/filter-dialog/filter-dialog-model.ts index c96b8ff..6d1cd45 100644 --- a/src/app/shared/filter-dialog/filter-dialog-model.ts +++ b/src/app/shared/filter-dialog/filter-dialog-model.ts @@ -7,8 +7,8 @@ // thin by design — it holds signals and wires the template, while all logic // below is unit-testable without Angular. -import { FilterJoinType, FilterOperation } from 'actslib'; -import type { EnumLike, IFilterCondition, IFilterDefinition } from 'actslib'; +import { FilterJoinType, FilterOperation, FilterUtility } from 'actslib'; +import type { EnumLike, FilterRoot, IFilterCondition, IFilterDefinition } from 'actslib'; // --------------------------------------------------------------------------- // Schema (per-page configuration) @@ -59,21 +59,37 @@ export interface FilterableProperty { prepareValue?: (value: string | number) => string | number; } -/** MAT_DIALOG_DATA: the schema, the filter in effect (seed), and labels. */ +/** MAT_DIALOG_DATA: the schema, the filter in effect (seed), and labels. + * The seed is a `FilterRoot` (actslib case 0/1/2): a group definition or a + * bare condition — either case-1 spelling re-seeds the same single leaf. */ export interface FilterDialogData { properties: FilterableProperty[]; - root?: IFilterDefinition; + root?: FilterRoot; /** deepest group level the toolbar offers; default 4 */ maxDepth?: number; /** dialog title i18n key; default 'common.editFilter' */ titleKey?: string; } -/** Submit result: the edited definition. Cancel returns undefined. */ +/** Submit result: the edited filter as a `FilterRoot` — the emitted tree + * passed through `FilterUtility.Simplify`, so a single-condition filter + * (case 1) leaves as a bare condition and the wrapper/group tree (case 2) + * as a definition. Case 0 (empty) cannot be submitted; clearing the filter + * is the pages' Clear Filter button. Cancel returns undefined. */ export interface FilterDialogResult { - root: IFilterDefinition; + root: FilterRoot; } +/** + * A fresh filter root: case 0 (match-all) — the cleared-filter state the + * pages' Clear Filter button installs directly. The dialog itself can never + * emit this (the `validateTree` emptyTree gate blocks case 0 at Submit). + */ +export const emptyFilterDefinition = (): IFilterDefinition => ({ + join: FilterJoinType.AND, + conditions: [], +}); + // --------------------------------------------------------------------------- // Editor state // --------------------------------------------------------------------------- @@ -221,13 +237,27 @@ const isFilterCondition = (m: IFilterCondition | IFilterDefinition): m is IFilte typeof (m as IFilterCondition).property === 'string'; /** - * True when a definition holds at least one condition anywhere (nested - * included). Dialog-emitted definitions never contain empty sub-groups, so - * this is defensive for hand-built seeds — pages use it to decide between the - * "new filter" and the summary menu label. + * A definition's members, tolerating a missing `conditions` key: actslib's + * own `Simplify`/`MatchFilter` treat `undefined` conditions as empty (JSON + * from persistence or a hand-built `{ join }` decodes without the key), so + * every walk here degrades the same way — to case 0 / match-all — instead of + * throwing during change detection. */ -export const hasActiveFilterDefinition = (def: IFilterDefinition | undefined): boolean => - !!def && def.conditions.some(c => (isFilterCondition(c) ? true : hasActiveFilterDefinition(c))); +const membersOf = (def: IFilterDefinition): Array => + def.conditions ?? []; + +/** + * True when a filter root holds at least one condition anywhere (nested + * included). A bare condition (case 1) is always active; dialog-emitted + * definitions never contain empty sub-groups, so the recursion is defensive + * for hand-built seeds — pages use this to decide between the "new filter" + * and the summary menu label. + */ +export const hasActiveFilterDefinition = (root: FilterRoot | undefined): boolean => + !!root && + (isFilterCondition(root) + ? true + : membersOf(root).some(c => (isFilterCondition(c) ? true : hasActiveFilterDefinition(c)))); /** A blank leaf for a property (all value slots cleared, first operator). */ export const emptyLeaf = (prop: FilterableProperty, newId: FilterIdGen): SharedFilterDialogLeaf => { @@ -264,11 +294,12 @@ const foldEnumOrGroup = ( schema: FilterableProperty[], newId: FilterIdGen ): SharedFilterDialogLeaf | null => { - if (def.join !== FilterJoinType.OR || def.conditions.length < 2) { + const members = membersOf(def); + if (def.join !== FilterJoinType.OR || members.length < 2) { return null; } - const conditions = def.conditions.filter(isFilterCondition); - if (conditions.length !== def.conditions.length) { + const conditions = members.filter(isFilterCondition); + if (conditions.length !== members.length) { return null; // a nested group is among the members — not a fold candidate } const first = conditions[0]; @@ -311,19 +342,27 @@ const seedCondition = ( }; /** - * Copy a caller's `IFilterDefinition` into editable nodes. Structure is + * Copy a caller's `FilterRoot` into editable nodes. The tree's top level is + * normalized to a SINGLE node — the actslib root itself: a bare condition + * (or any chain of 1-member definition wrappers, the case-1 spellings) + * seeds one condition leaf; a 2+ member definition seeds one GROUP node + * (the case-2 root, its join carried over); an empty/absent seed leaves the + * wrapper without a member (the transient empty tree). The wrapper is inert + * scaffold — its join is never evaluated and `FilterUtility.Simplify` + * unwraps its single member at the Submit boundary — kept only so the + * recursive helpers have a node to work from. Structure below the top is * preserved to any depth (never flattened); enum OR-groups and custom-op - * conditions fold back into single leaves so re-editing is lossless. The - * input definition is never mutated. + * conditions fold back into single leaves (the top node included) so + * re-editing is lossless. The input is never mutated. */ export const seedTree = ( - root: IFilterDefinition | undefined, + root: FilterRoot | undefined, schema: FilterableProperty[], newId: FilterIdGen ): SharedFilterDialogNode => { const seedNode = (def: IFilterDefinition): SharedFilterDialogNode => { const members: SharedFilterDialogMember[] = []; - for (const member of def.conditions) { + for (const member of membersOf(def)) { if (isFilterCondition(member)) { members.push(seedCondition(member, schema, newId)); continue; @@ -342,10 +381,27 @@ export const seedTree = ( }; }; - if (!root) { - return { id: newId(), join: FilterJoinType.AND, members: [] }; - } - return seedNode(root); + // Normalize the seed to a single top node (the root itself), or none. + const seedTop = (r: FilterRoot): SharedFilterDialogMember | null => { + let current: IFilterCondition | IFilterDefinition = r; + while (!isFilterCondition(current) && membersOf(current).length === 1) { + current = membersOf(current)[0]; // 1-member wrappers are case-1 spellings + } + if (isFilterCondition(current)) { + return seedCondition(current, schema, newId); + } + if (membersOf(current).length === 0) { + return null; // case 0 — the tree gets no top node + } + // 2+ conditions: the definition becomes THE root group node. + return foldEnumOrGroup(current, schema, newId) ?? seedNode(current); + }; + const top = root ? seedTop(root) : null; + return { + id: newId(), + join: FilterJoinType.AND, // inert scaffold join (a single-member wrapper never evaluates it) + members: top ? [top] : [], + }; }; // --------------------------------------------------------------------------- @@ -432,9 +488,12 @@ export const emitLeaf = ( /** * Emit the editor tree to an actslib definition. Incomplete leaves and - * emptied sub-groups are dropped (an empty group would match everything); - * the root always yields a definition (empty conditions = match-all = the - * clear-filter state). Validation guarantees nothing is dropped at Submit. + * emptied sub-groups are dropped (an empty group would match everything). + * The dialog's Submit boundary passes this through + * `FilterUtility.Simplify`, so a single-condition filter leaves as a bare + * condition (case 1); the raw wrapper is what the live preview renders. + * Validation guarantees nothing is dropped and the root is non-empty at + * Submit. */ export const emitTree = (root: SharedFilterDialogNode, schema: FilterableProperty[]): IFilterDefinition => { const emitNode = (node: SharedFilterDialogNode): Array => { @@ -584,21 +643,29 @@ export const removeMember = (root: SharedFilterDialogNode, memberId: number): Sh export interface FilterTreeValidation { /** ids of leaves whose active value editor holds no usable value */ missingValueIds: number[]; - /** ids of nested groups with fewer than two members (root exempt) */ + /** ids of rendered groups (the single top row included) with fewer than two members */ invalidGroupIds: number[]; - /** true when the tree can be submitted */ + /** true when the root holds no members (case 0 — clearing is the pages' job) */ + emptyTree: boolean; + /** true when the tree can be submitted (case 1 or case 2, all values present) */ canSubmit: boolean; } /** - * Validate the editor tree: every leaf must have a value (except valueless - * custom ops and the between low<=high rule), and every NESTED group must - * branch (>= 2 members). The root is exempt — 0 members clears the filter, - * 1 is a plain single-condition filter. + * Validate the editor tree against the three-case taxonomy (see + * docs/filter-hierarchy-contract.md): the invisible wrapper root must carry + * at least one member (case 0 is not the dialog's business — the pages' + * Clear Filter button owns the empty filter), every leaf must have a value + * (except valueless custom ops and the between low<=high rule), and every + * RENDERED group must branch (>= 2 members) — the single top GROUP row + * included, which is why `walk` only exempts the wrapper itself. The + * wrapper holds at most one node by construction, so its exemption is + * structural: a 1-member wrapper IS case 1 (a lone condition leaf). */ export const validateTree = (root: SharedFilterDialogNode, schema: FilterableProperty[]): FilterTreeValidation => { const missingValueIds: number[] = []; const invalidGroupIds: number[] = []; + const emptyTree = root.members.length === 0; const checkBetweenOrder = (leaf: SharedFilterDialogLeaf): boolean => { const [low, high] = leaf.between; if (low === null || high === null || low === '' || high === '') { @@ -638,7 +705,8 @@ export const validateTree = (root: SharedFilterDialogNode, schema: FilterablePro return { missingValueIds, invalidGroupIds, - canSubmit: missingValueIds.length === 0 && invalidGroupIds.length === 0, + emptyTree, + canSubmit: !emptyTree && missingValueIds.length === 0 && invalidGroupIds.length === 0, }; }; @@ -714,25 +782,31 @@ const describeCondition = ( }; /** - * Human-readable rendering of a definition in the same parenthesized notation - * the filter menu label uses. Multi-member sub-groups get parentheses; + * Human-readable rendering of a filter root in the same parenthesized + * notation the filter menu label uses — a bare condition (case 1) renders + * like its 1-member wrapper. Multi-member sub-groups get parentheses; * an OR group of same-enum-property `Equal`s renders as one `prop a/b/c` * phrase (the enum fold's display). `maxLength` caps with an ellipsis. */ export const summarizeFilterDefinition = ( - def: IFilterDefinition, + root: FilterRoot, schema: FilterableProperty[], labels: FilterSummaryLabels, maxLength = Number.POSITIVE_INFINITY ): string => { + // actslib's normalizer (the exact inverse of the Simplify Submit uses): if + // the library ever changes the case-1 wrapper spelling, the label follows + // the evaluation instead of drifting from it. + const def: IFilterDefinition = FilterUtility.ToDefinition(root); const foldEnumParts = (group: IFilterDefinition): string[] | null => { - const folded = group.conditions.every( + const members = membersOf(group); + const folded = members.every( c => isFilterCondition(c) && c.operation === FilterOperation.Equal ); - if (!folded || group.conditions.length < 2) { + if (!folded || members.length < 2) { return null; } - const conditions = group.conditions as IFilterCondition[]; + const conditions = members as IFilterCondition[]; const prop = findProperty(schema, conditions[0].property); if (!prop || prop.kind !== 'enum') { return null; @@ -754,7 +828,7 @@ export const summarizeFilterDefinition = ( return folded.join(' '); } const parts: string[] = []; - for (const member of group.conditions) { + for (const member of membersOf(group)) { if (isFilterCondition(member)) { parts.push(describeCondition(member, schema, labels)); continue; @@ -778,7 +852,10 @@ export const summarizeFilterDefinition = ( if (joined.length <= maxLength) { return joined; } - return `${joined.slice(0, maxLength - 1).trimEnd()}…`; + // Slice by CODE POINT: a UTF-16 slice can cut an astral-plane character + // (e.g. a CJK Ext-B ideograph in a classical-name filter) between its + // surrogates, which the menu label would render as U+FFFD. + return `${Array.from(joined).slice(0, maxLength - 1).join('').trimEnd()}…`; }; /** diff --git a/src/app/shared/filter-dialog/filter-dialog.component.html b/src/app/shared/filter-dialog/filter-dialog.component.html index d38bfe7..6e3d995 100644 --- a/src/app/shared/filter-dialog/filter-dialog.component.html +++ b/src/app/shared/filter-dialog/filter-dialog.component.html @@ -4,6 +4,11 @@

{{ t(titleKey) }}

+
+
@@ -59,6 +67,9 @@

{{ t(titleKey) }}

{{ t('common.filterGroup') }}

{{ t('common.filterJoinHint') }}

+ @for (join of joinOptions; track join.value) { diff --git a/src/app/shared/filter-dialog/filter-dialog.component.spec.ts b/src/app/shared/filter-dialog/filter-dialog.component.spec.ts index e64fcb2..0b9ca11 100644 --- a/src/app/shared/filter-dialog/filter-dialog.component.spec.ts +++ b/src/app/shared/filter-dialog/filter-dialog.component.spec.ts @@ -14,7 +14,11 @@ import type { EnumLike, IFilterCondition, IFilterDefinition } from 'actslib'; import { of } from 'rxjs'; import { vi } from 'vitest'; -import type { FilterDialogData, FilterableProperty } from './filter-dialog-model'; +import type { + FilterDialogData, + FilterableProperty, + SharedFilterDialogNode, +} from './filter-dialog-model'; import { SharedFilterDialogComponent } from './filter-dialog.component'; function mockTranslocoService() { @@ -91,6 +95,7 @@ describe('SharedFilterDialogComponent', () => { const component = () => fixture.componentInstance; const rootNode = () => component().root(); const rows = () => fixture.nativeElement.querySelectorAll('.fdlg-node-row'); + const rowList = () => rows() as NodeListOf; const toolbarButtons = () => fixture.nativeElement.querySelectorAll( '.fdlg-tree-toolbar button' @@ -99,6 +104,10 @@ describe('SharedFilterDialogComponent', () => { fixture.nativeElement.querySelectorAll( 'mat-dialog-actions button' ) as NodeListOf; + /** The toolbar's disabled flags, in order: [+ condition, + group, delete]. */ + const disabledStates = (): boolean[] => Array.from(toolbarButtons()).map(b => b.disabled); + /** The tree's single top node — the actslib root (case-1 leaf, case-2 group). */ + const topNode = () => rootNode().members[0] as SharedFilterDialogNode; const seedDef: IFilterDefinition = { join: FilterJoinType.AND, @@ -135,34 +144,143 @@ describe('SharedFilterDialogComponent', () => { } describe('rendering', () => { - it('seeds the tree: rows, labels and the root selected in the detail pane', async () => { + it('(1) opens in case 1: ONE condition node — the root — selected, delete-only armed', async () => { + await createWith({ properties: SCHEMA }); + fixture.detectChanges(); + const root = rootNode(); + // The editor tree IS the actslib root: exactly one node (the scaffolded + // condition), no wrapper row above it. + expect(root.members.length).toBe(1); + expect(rowList().length).toBe(1); + // A blank leaf labels with just its property name (describeMember fallback). + expect(rowList()[0].textContent).toContain('test.word'); + // The node is selected: its condition editor fills the detail pane. + expect(fixture.nativeElement.querySelector('.fdlg-text input')).toBeTruthy(); + expect(fixture.nativeElement.querySelector('.fdlg-join')).toBeNull(); + // A selected CONDITION arms delete only. + expect(disabledStates()).toEqual([true, true, false]); + // Blank value -> Submit disabled by the missing-value rule. + expect(component().validation().missingValueIds).toEqual([ + (root.members[0] as { id: number }).id, + ]); + expect(component().canSubmit()).toBe(false); + }); + + it('an empty definition seed (the pages\' cleared filter) scaffolds the same way', async () => { + await createWith({ properties: SCHEMA, root: { conditions: [] } }); + fixture.detectChanges(); + expect(rootNode().members.length).toBe(1); + expect(rowList().length).toBe(1); // the case-1 one-node tree + expect(fixture.nativeElement.querySelector('.fdlg-text input')).toBeTruthy(); + // A non-empty seed is copied in untouched (no scaffold added): + // the 5-row seedDef test below covers that. + }); + + it('(2) delete empties the tree (the adds re-arm); + condition restores case 1', async () => { + await createWith({ properties: SCHEMA }); + fixture.detectChanges(); + toolbarButtons()[2].click(); // delete the selected condition + fixture.detectChanges(); + // No rows, no selection: the transient empty state the toolbar's + // insert buttons own. + expect(rowList().length).toBe(0); + expect(component().selectedId()).toBeNull(); + expect(disabledStates()).toEqual([false, false, true]); // adds armed, delete off + expect(fixture.nativeElement.querySelector('.fdlg-detail-empty')).toBeTruthy(); + expect(component().canSubmit()).toBe(false); // case 0 is the Clear Filter button's job + + toolbarButtons()[0].click(); // + condition from the empty state + fixture.detectChanges(); + expect(rowList().length).toBe(1); // back to the one-node case-1 tree + expect(component().selectedId()).toBe(rootNode().members[0].id); + expect(disabledStates()).toEqual([true, true, false]); + expect(fixture.nativeElement.querySelector('.fdlg-text input')).toBeTruthy(); + }); + + it('(3) from the empty tree + group roots a selected group; + condition adds its first child', async () => { + await createWith({ properties: SCHEMA }); + fixture.detectChanges(); + toolbarButtons()[2].click(); // delete the case-1 node -> empty + fixture.detectChanges(); + toolbarButtons()[1].click(); // + group + fixture.detectChanges(); + expect(rowList().length).toBe(1); // the group IS the single root node + const groupId = topNode().id; + expect(topNode().members).toEqual([]); + expect(component().selectedId()).toBe(groupId); + expect(disabledStates()).toEqual([false, false, false]); // a GROUP arms all three + expect(fixture.nativeElement.querySelector('.fdlg-join')).toBeTruthy(); + + toolbarButtons()[0].click(); // + condition -> the group's FIRST child + fixture.detectChanges(); + expect(rowList().length).toBe(2); // group row + nested child row + const child = (component().memberById(groupId) as SharedFilterDialogNode).members[0]; + expect(component().selectedId()).toBe(child.id); // the child takes selection + expect(disabledStates()).toEqual([true, true, false]); // it is a condition + expect(rowList()[1].textContent).toContain('test.word'); + + rowList()[0].click(); // back to the group: all three armed again + fixture.detectChanges(); + expect(disabledStates()).toEqual([false, false, false]); + }); + + it('the root group node edits its join in the detail pane and emits as the case-2 root', async () => { + await createWith({ properties: SCHEMA, root: seedDef }); + fixture.detectChanges(); + // NgModel writes its model/disabled state on a microtask. + await fixture.whenStable(); + fixture.detectChanges(); + const joinSelect = (): HTMLElement => + fixture.nativeElement.querySelector('.fdlg-join mat-select'); + expect(joinSelect()).toBeTruthy(); + expect(joinSelect().getAttribute('aria-disabled')).not.toBe('true'); + expect(joinSelect().textContent).toContain('common.joinAnd'); + + choose('.fdlg-join', 'common.joinOr'); + component().onSubmit(); + // Simplify unwraps the 1-member scaffold: the emitted root IS the + // edited group (case 2), its join and members intact. + const arg = closeSpy.mock.calls[0][0] as { root: IFilterDefinition }; + expect(arg.root.join).toBe(FilterJoinType.OR); + expect(arg.root.conditions.length).toBe(2); + }); + + it('seeds a case-2 definition as ONE root group row with nested members', async () => { await createWith({ properties: SCHEMA, root: seedDef }); fixture.detectChanges(); - // The seed has root(1) + enword leaf(1) + group(1) + two rating leaves(2) = 5 rows. - expect(rows().length).toBe(5); + // Normalized to a single top node: the root GROUP row, then enword + // leaf, the OR group, and its two rating leaves. + expect(rowList().length).toBe(5); const host: HTMLElement = fixture.nativeElement; const labels = Array.from(host.querySelectorAll('.fdlg-node-label')); - // Row order: [0] root group, [1] enword leaf, [2] OR group, [3]+[4] its leaves. expect(labels[0].textContent).toContain('common.filterGroup (common.joinAnd)'); expect(labels[1].textContent).toContain('test.word common.opContains app'); expect(labels[2].textContent?.trim()).toContain('(common.joinOr)'); - // Root selected initially -> group editor (join select) shows. + // The root node is selected on open -> the group join editor, and the + // group kind arms all three toolbar buttons. + expect(component().selectedId()).toBe(topNode().id); expect(fixture.nativeElement.querySelector('.fdlg-join')).toBeTruthy(); + expect(disabledStates()).toEqual([false, false, false]); }); it('re-renders the tree after toolbar inserts (reference trackBy guard)', async () => { await createWith({ properties: SCHEMA, root: seedDef }); fixture.detectChanges(); - expect(rows().length).toBe(5); + expect(rowList().length).toBe(5); - toolbarButtons()[0].click(); // +cond + toolbarButtons()[0].click(); // +cond into the selected root group fixture.detectChanges(); - expect(rows().length).toBe(6); + expect(rowList().length).toBe(6); - toolbarButtons()[1].click(); // +group (root still reachable via the new leaf's parent) + // The inserted leaf holds the selection, so +group is off until the + // root group is picked again — inserts target GROUP rows only. + expect(toolbarButtons()[1].disabled).toBe(true); + rowList()[0].click(); + fixture.detectChanges(); + toolbarButtons()[1].click(); // +group into the root group fixture.detectChanges(); - expect(rows().length).toBe(8); // group node + its default row + expect(rowList().length).toBe(7); // one click adds exactly one node (childless group) }); it('renders the live preview uncapped', async () => { @@ -179,13 +297,14 @@ describe('SharedFilterDialogComponent', () => { it('property switch resets the operator and value; the tree label follows', async () => { await createWith({ properties: SCHEMA, root: seedDef }); fixture.detectChanges(); - const wordRow: NodeListOf = rows(); - wordRow[1].click(); // enword leaf + + rowList()[1].click(); // enword leaf (first row under the root group) fixture.detectChanges(); + expect(disabledStates()).toEqual([true, true, false]); // a condition: delete only choose('.fdlg-property', 'test.rating'); - const leaf = rootNode().members[0]; + const leaf = topNode().members[0]; expect('propertyKey' in leaf ? leaf.propertyKey : '').toBe('rating'); expect(component().describeNode(leaf)).not.toContain('app'); // Root identity changed (the edit flowed through the signal). @@ -204,8 +323,7 @@ describe('SharedFilterDialogComponent', () => { }, }); fixture.detectChanges(); - const wordRow: NodeListOf = rows(); - wordRow[1].click(); // first rating leaf + rowList()[1].click(); // first rating leaf (row under the root group) fixture.detectChanges(); choose('.fdlg-operator', 'common.opBetween'); @@ -214,7 +332,7 @@ describe('SharedFilterDialogComponent', () => { typeInto(bounds[0], '1'); typeInto(bounds[1], '5'); - const leaf = rootNode().members[0]; + const leaf = topNode().members[0]; expect('between' in leaf).toBe(true); component().onSubmit(); const arg = closeSpy.mock.calls[0][0] as { root: IFilterDefinition }; @@ -243,8 +361,7 @@ describe('SharedFilterDialogComponent', () => { }, }); fixture.detectChanges(); - const firstRow: NodeListOf = rows(); - firstRow[1].click(); // the enum leaf + rowList()[1].click(); // the enum leaf (row under the root group) fixture.detectChanges(); const checkboxes: NodeListOf = fixture.nativeElement.querySelectorAll( @@ -256,7 +373,7 @@ describe('SharedFilterDialogComponent', () => { // Check 'blue' too — the leaf now holds a two-value selection. checkboxes[1].click(); fixture.detectChanges(); - const leaf = rootNode().members[0]; + const leaf = topNode().members[0]; expect('choices' in leaf ? leaf.choices : []).toEqual(['red', 'blue']); component().onSubmit(); @@ -273,14 +390,13 @@ describe('SharedFilterDialogComponent', () => { it('custom operator hides the value editor and stays submittable', async () => { await createWith({ properties: SCHEMA, root: seedDef }); fixture.detectChanges(); - const firstRow: NodeListOf = rows(); - firstRow[1].click(); // enword leaf (has the custom op) + rowList()[1].click(); // enword leaf (has the custom op) fixture.detectChanges(); choose('.fdlg-operator', 'test.opIsPhrase'); expect(fixture.nativeElement.querySelector('.fdlg-text input')).toBeNull(); - const leaf = rootNode().members[0]; + const leaf = topNode().members[0]; expect('operator' in leaf ? leaf.operator : '').toBe('isPhrase'); expect(component().canSubmit()).toBe(true); // valueless -> never missing }); @@ -288,14 +404,17 @@ describe('SharedFilterDialogComponent', () => { it('edits flow through the root signal (ancestor objects are replaced)', async () => { await createWith({ properties: SCHEMA, root: seedDef }); const before = rootNode(); - const groupBefore = before.members[1]; + const topBefore = topNode(); + const groupBefore = topBefore.members[1] as SharedFilterDialogNode; expect('members' in groupBefore).toBe(true); - component().onJoinChange((groupBefore as { id: number }).id, FilterJoinType.AND); + component().onJoinChange(groupBefore.id, FilterJoinType.AND); expect(rootNode()).not.toBe(before); - const groupAfter = rootNode().members[1]; + const topAfter = topNode(); + expect(topAfter).not.toBe(topBefore); + const groupAfter = topAfter.members[1] as SharedFilterDialogNode; expect(groupAfter).not.toBe(groupBefore); - expect('join' in groupAfter ? groupAfter.join : '').toBe(FilterJoinType.AND); + expect(groupAfter.join).toBe(FilterJoinType.AND); }); }); @@ -309,7 +428,7 @@ describe('SharedFilterDialogComponent', () => { // Select the word leaf and wipe its text -> missing value -> Submit // disabled and the hint shown in the detail pane. - (rows() as NodeListOf)[1].click(); + rowList()[1].click(); fixture.detectChanges(); const input: HTMLInputElement = fixture.nativeElement.querySelector('.fdlg-text input'); typeInto(input, ' '); @@ -319,21 +438,24 @@ describe('SharedFilterDialogComponent', () => { ); }); - it('an empty nested group is flagged and blocks submit; the root is exempt', async () => { - await createWith({ - properties: SCHEMA, - root: { - join: FilterJoinType.AND, - conditions: [{ property: 'rating', operation: FilterOperation.Equal, lowValue: 2 }], - }, - }); + it('a childless group root is flagged and blocks submit; so does the empty tree', async () => { + await createWith({ properties: SCHEMA }); + fixture.detectChanges(); + toolbarButtons()[2].click(); // delete the case-1 node -> empty tree fixture.detectChanges(); + expect(component().canSubmit()).toBe(false); // case 0 — Clear Filter's job - // Insert a group (starts with one row, so it is already invalid). - toolbarButtons()[1].click(); + toolbarButtons()[1].click(); // + group -> one childless root group fixture.detectChanges(); + expect(rowList().length).toBe(1); + // An empty group violates the >=2 rule, so it is flagged and blocks submit. expect(actionButtons()[1].disabled).toBe(true); expect(fixture.nativeElement.querySelector('.fdlg-node-invalid')).toBeTruthy(); + + toolbarButtons()[2].click(); // delete the group -> back to the empty tree + fixture.detectChanges(); + expect(rowList().length).toBe(0); + expect(disabledStates()).toEqual([false, false, true]); }); it('onSubmit closes with the emitted definition; onCancel closes undefined', async () => { @@ -351,15 +473,140 @@ describe('SharedFilterDialogComponent', () => { expect(closeSpy).toHaveBeenCalledWith(); }); + it('a single-condition filter submits as a BARE condition (actslib case 1)', async () => { + await createWith({ properties: SCHEMA }); + fixture.detectChanges(); + // The dialog opens scaffolded with one BLANK condition — Submit is + // disabled by the missing-value rule until it is filled. + expect(component().canSubmit()).toBe(false); + expect(actionButtons()[1].disabled).toBe(true); + + const input = fixture.nativeElement.querySelector('.fdlg-text input') as HTMLInputElement; + expect(input).toBeTruthy(); + input.value = ' APPLE '; + input.dispatchEvent(new Event('input')); + fixture.detectChanges(); + + expect(component().canSubmit()).toBe(true); + actionButtons()[1].click(); + const arg = closeSpy.mock.calls[0][0] as { root: unknown }; + // Simplify at the boundary: the scaffold's single member leaves as the + // bare condition (prepareValue folded the text). + expect(arg.root).toEqual({ + property: 'enword', + operation: FilterOperation.BeginsWith, + lowValue: 'apple', + }); + }); + + it('a bare-condition seed renders one leaf and re-submits as the same bare condition', async () => { + await createWith({ + properties: SCHEMA, + root: { property: 'enword', operation: FilterOperation.Contains, lowValue: 'app' }, + }); + fixture.detectChanges(); + expect(rowList().length).toBe(1); // the one-node case-1 tree + expect(disabledStates()).toEqual([true, true, false]); + expect(component().canSubmit()).toBe(true); + component().onSubmit(); + const arg = closeSpy.mock.calls[0][0] as { root: unknown }; + expect(arg.root).toEqual({ property: 'enword', operation: FilterOperation.Contains, lowValue: 'app' }); + }); + + it('a 1-member group blocks submit even with its leaf filled (case 2 groups must branch)', async () => { + await createWith({ properties: SCHEMA }); + fixture.detectChanges(); + toolbarButtons()[2].click(); // delete the case-1 node -> empty + fixture.detectChanges(); + toolbarButtons()[1].click(); // + group + fixture.detectChanges(); + toolbarButtons()[0].click(); // + condition -> the group's FIRST child + fixture.detectChanges(); + expect(rowList().length).toBe(2); // group + child + typeInto(fixture.nativeElement.querySelector('.fdlg-text input') as HTMLInputElement, 'apple'); + // The leaf is complete, but the 1-member group is the violation + // (nested groups must branch) with its warning. + expect(component().validation().missingValueIds).toEqual([]); + expect(component().canSubmit()).toBe(false); + expect(fixture.nativeElement.querySelector('.fdlg-node-warn')).toBeTruthy(); + }); + it('the depth cap disables +group below the configured maximum', async () => { await createWith({ properties: SCHEMA, root: seedDef, maxDepth: 2 }); fixture.detectChanges(); - // Select the nested group (level 2): inserting another group would exceed the cap. - const firstRow: NodeListOf = rows(); - firstRow[2].click(); + // Select the nested OR group (row 3: root group, leaf, it): inserting + // another group would exceed the cap. The cap counts VISIBLE levels — + // the wrapper is level 0, the root group is level 1, its children 2. + rowList()[2].click(); fixture.detectChanges(); expect(component().canInsertGroup()).toBe(false); expect(toolbarButtons()[1].disabled).toBe(true); }); + + it('at maxDepth 2 the root group still accepts a nested group (condA AND (condB OR condC))', async () => { + // The contract's flagship two-level example must stay expressible at + // the shallowest cap that describes it: root group (level 1) + one + // nested group (level 2). + await createWith({ properties: SCHEMA, root: seedDef, maxDepth: 2 }); + fixture.detectChanges(); + rowList()[0].click(); // the root group + fixture.detectChanges(); + expect(component().canInsertGroup()).toBe(true); + expect(toolbarButtons()[1].disabled).toBe(false); + }); + + it('at maxDepth 1 even the root group cannot add a nested group', async () => { + await createWith({ properties: SCHEMA, root: seedDef, maxDepth: 1 }); + fixture.detectChanges(); + rowList()[0].click(); // the root group (visible level 1) + fixture.detectChanges(); + expect(component().canInsertGroup()).toBe(false); + }); + }); + + describe('toolbar logic', () => { + it('arms the buttons per the selected node kind', async () => { + await createWith({ properties: SCHEMA, root: seedDef }); + fixture.detectChanges(); + + rowList()[1].click(); // a condition leaf: delete only + fixture.detectChanges(); + expect(disabledStates()).toEqual([true, true, false]); + + rowList()[2].click(); // a nested group (below the depth cap): all three + fixture.detectChanges(); + expect(disabledStates()).toEqual([false, false, false]); + + rowList()[0].click(); // the root group: all three too + fixture.detectChanges(); + expect(disabledStates()).toEqual([false, false, false]); + }); + + it('the delete button announces the selected node kind (L2)', async () => { + await createWith({ properties: SCHEMA, root: seedDef }); + fixture.detectChanges(); + const del = () => toolbarButtons()[2]; + // The root GROUP is selected on open: the button removes a group. + expect(del().getAttribute('aria-label')).toBe('common.removeFilterGroup'); + expect(del().getAttribute('title')).toBe('common.removeFilterGroup'); + rowList()[1].click(); // a condition leaf + fixture.detectChanges(); + expect(del().getAttribute('aria-label')).toBe('common.removeFilterCondition'); + expect(del().getAttribute('title')).toBe('common.removeFilterCondition'); + }); + + it('deleting a nested member returns the selection to its parent group', async () => { + await createWith({ properties: SCHEMA, root: seedDef }); + fixture.detectChanges(); + const groupId = topNode().members[1].id; // the nested OR group + rowList()[3].click(); // a rating leaf inside the OR group + fixture.detectChanges(); + toolbarButtons()[2].click(); // delete it + fixture.detectChanges(); + // The parent group keeps the selection (the tree is not empty), and a + // group kind arms all three again. + expect(component().selectedId()).toBe(groupId); + expect(disabledStates()).toEqual([false, false, false]); + }); }); }); diff --git a/src/app/shared/filter-dialog/filter-dialog.component.ts b/src/app/shared/filter-dialog/filter-dialog.component.ts index 7067ae7..c3599dd 100644 --- a/src/app/shared/filter-dialog/filter-dialog.component.ts +++ b/src/app/shared/filter-dialog/filter-dialog.component.ts @@ -15,7 +15,7 @@ import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatTree, MatTreeNodeDef, MatTreeNodeOutlet, MatNestedTreeNode } from '@angular/material/tree'; import { TranslocoModule, TranslocoService } from '@jsverse/transloco'; -import { FilterJoinType } from 'actslib'; +import { FilterJoinType, FilterUtility } from 'actslib'; import { appendMember, @@ -52,19 +52,39 @@ import type { /** * Project-wide filter condition editor (docs/reusable-filter-dialog-design.md): - * pages pass a `FilterableProperty[]` schema plus the actslib - * `IFilterDefinition` in effect, and get an edited definition back on Submit - * (`undefined` on Cancel/backdrop/Esc — the caller keeps its previous filter). + * pages pass a `FilterableProperty[]` schema plus the actslib `FilterRoot` in + * effect, and get an edited `FilterRoot` back on Submit (`undefined` on + * Cancel/backdrop/Esc — the caller keeps its previous filter). * * The layout follows the vocabulary filter dialog it generalizes: a - * `mat-tree` navigator (left) renders the condition tree, the detail pane - * (right) edits the selected member — a group's AND/OR join, or a leaf's - * property / operator / value (the value editor dispatches by kind: - * text, number, date, Between's two inputs, or the enum multiple-choice list, - * with valueless custom operators) — and a draggable splitter sizes the panes. - * Insert/delete live in a toolbar above the tree and target the selection; - * Submit stays disabled while validation fails (missing values, nested groups - * with fewer than two members — root exempt, flagged in the tree). + * `mat-tree` navigator (left) renders the condition tree exactly as the + * actslib `FilterRoot` reads — the top level holds AT MOST ONE node, the + * root itself (case 1: a single condition row; case 2: a single group row + * with its members nested below; empty: no rows). The internal wrapper is + * never rendered and its join is forever inert (seedTree normalizes any + * seed to a 0-or-1-member scaffold; `Simplify` unwraps it at Submit). The + * detail pane (right) edits the selected node — a group's AND/OR join, or a + * leaf's property / operator / value (the value editor dispatches by kind: + * text, number, date, Between's two inputs, or the enum multiple-choice + * list, with valueless custom operators) — and a draggable splitter sizes + * the panes. + * The toolbar above the tree holds exactly THREE buttons — + condition, + * + group, delete — enabled by the selected node's kind: NOTHING selected + * (the transient empty tree) arms the two inserts (delete off — there is + * nothing to remove), a selected CONDITION arms delete only, a selected + * GROUP arms all three (it becomes the insert target; + group respects the + * depth cap). So a node is always selected unless the tree is empty: + * inserts select the new node, and deleting a member returns the selection + * to its parent group — or to nothing when the tree just emptied, where + * the inserts re-arm (delete -> + group -> + condition is the growth path + * from any state). + * A dialog seeded empty (a "new filter") opens SCAFFOLDED with one blank + * condition — case 1: one node, selected, delete-only armed. Submit stays + * disabled while validation fails (empty tree — clearing is the pages' + * Clear Filter button — missing values, or groups with fewer than two + * members, the single top GROUP row included; flagged in the tree). Submit emits a + * `FilterRoot`: a bare condition for a single-condition filter (actslib + * case 1, via `FilterUtility.Simplify`), a definition otherwise (case 2). * * CDK invariants this component relies on (§6.3 of the design — do not * "simplify"): `[trackBy]` is object REFERENCE (nested children are read once @@ -108,8 +128,26 @@ export class SharedFilterDialogComponent { readonly properties = this.data.properties; readonly maxDepth = this.data.maxDepth ?? 4; readonly titleKey = this.data.titleKey ?? 'common.editFilter'; - readonly root = signal(seedTree(this.data.root, this.data.properties, this.newId)); - readonly selectedId = signal(this.root().id); + readonly root = signal(this.buildInitialTree()); + // The navigator's top rows are the (at most one) root NODE — never the + // unrendered wrapper: open with that node selected, or no selection for an + // empty tree (where the toolbar's inserts are armed). + readonly selectedId = signal(this.root().members[0]?.id ?? null); + + /** + * Seed the caller's filter (any `FilterRoot` shape — `seedTree` normalizes + * it to a single top node). An empty seed — the "new filter" case — opens + * scaffolded with ONE blank condition: case 1, exactly one node. Submit is + * disabled by the missing-value rule until the leaf is filled. + */ + private buildInitialTree(): SharedFilterDialogNode { + const tree = seedTree(this.data.root, this.data.properties, this.newId); + if (tree.members.length > 0) { + return tree; + } + const first = this.data.properties[0]; + return first ? { ...tree, members: [emptyLeaf(first, this.newId)] } : tree; + } /** Labeler for the model's renderers (tree rows + preview). */ private readonly labels: FilterSummaryLabels = { @@ -124,8 +162,14 @@ export class SharedFilterDialogComponent { // --- mat-tree wiring (see the CDK invariants in the class doc) ------------ - /** Top-level rows of the navigator; a new array only when the tree changes. */ - readonly treeData = computed(() => [this.root()]); + /** + * The navigator's top row: the tree's single root NODE (a case-1 condition + * or a case-2 group with nested rows under it) — or nothing while the tree + * is empty. The wrapper itself is never a row (it holds at most one + * member, so its join is inert and has no UI). A new array only when the + * tree changes. + */ + readonly treeData = computed(() => this.root().members); readonly childrenAccessor = (member: SharedFilterDialogMember): SharedFilterDialogMember[] => isFilterDialogNode(member) ? member.members : []; @@ -159,27 +203,43 @@ export class SharedFilterDialogComponent { return root.id === memberId ? root : findMember(root, memberId); } - /** Member currently reflected in the detail pane. */ + /** Member currently reflected in the detail pane (null: empty tree — + * the insert-anchor state, where nothing exists to select). */ selectedMember(): SharedFilterDialogMember | null { - return this.memberById(this.selectedId()); + const id = this.selectedId(); + return id === null ? null : this.memberById(id); } - /** True when the selection is not the root (which can never be deleted). */ + /** True when a node is selected (the empty tree has none — delete off). */ canDeleteSelected(): boolean { + return this.selectedMember() !== null; + } + + /** + * The delete button announces what it removes: a selected CONDITION (leaf) + * deletes a condition, anything else (a group, or the disabled no-selection + * state) keeps the group wording. + */ + deleteSelectedLabelKey(): string { const selected = this.selectedMember(); - return selected !== null && selected.id !== this.root().id; + return selected && !isFilterDialogNode(selected) + ? 'common.removeFilterCondition' + : 'common.removeFilterGroup'; } /** - * The group that accepts toolbar inserts: the selected group itself, or the - * parent of the selected leaf; null when nothing is selected. + * The group that accepts toolbar inserts: a selected GROUP (the case-2 + * root node or any nested group), or — with NOTHING selected (the empty + * tree) — the scaffold root, so the insert becomes THE single top node. + * A selected CONDITION targets nothing: its parent is not the selection, + * so both add buttons stay disabled for it. */ insertTargetId(): number | null { const selected = this.selectedMember(); if (!selected) { - return null; + return this.root().id; } - return isFilterDialogNode(selected) ? selected.id : parentIdOf(this.root(), selected.id); + return isFilterDialogNode(selected) ? selected.id : null; } canInsertCondition(): boolean { @@ -196,8 +256,14 @@ export class SharedFilterDialogComponent { return !!target && isFilterDialogNode(target) && this.canAddGroup(target); } + /** + * The depth cap counts VISIBLE group levels: the unrendered scaffold + * wrapper is level 0, so the single top node the user sees sits at level 1 + * and `maxDepth` is exactly the deepest group level the toolbar offers + * (`FilterDialogData.maxDepth`). + */ private canAddGroup(node: SharedFilterDialogNode): boolean { - return depthOf(this.root(), node.id, 1) < this.maxDepth; + return depthOf(this.root(), node.id, 0) < this.maxDepth; } /** Insert a blank condition (first property) into the target group and select it. */ @@ -212,11 +278,15 @@ export class SharedFilterDialogComponent { this.selectedId.set(leaf.id); } - /** Insert an OR-joined group (one default condition) into the target group and select it. */ + /** + * Insert an OR-joined, CHILDLESS group into the target group and select it. + * One click adds exactly one node: the group starts empty and carries the + * >=2-members warning until the user fills it (+ condition targets the + * selected group), rather than seeding a phantom placeholder row. + */ onInsertGroup(): void { const targetId = this.insertTargetId(); - const first = this.properties[0]; - if (targetId === null || !first) { + if (targetId === null) { return; } const target = this.memberById(targetId); @@ -226,16 +296,23 @@ export class SharedFilterDialogComponent { const group: SharedFilterDialogNode = { id: this.newId(), join: FilterJoinType.OR, - members: [emptyLeaf(first, this.newId)], + members: [], }; this.root.update(root => appendMember(root, targetId, group)); this.selectedId.set(group.id); } - /** Delete the selected member (leaf, or group with its subtree); selection moves to the parent. */ + /** + * Delete the selected node (leaf, or group with its subtree). Selection + * moves to the parent group; when the parent is the unrendered scaffold + * root, it lands on the root's surviving first member — or on NOTHING + * once the tree has emptied, the insert-anchor state where both add + * buttons re-arm (the top level holds at most one node, so deleting the + * top node always empties the tree). + */ onDeleteSelected(): void { const selected = this.selectedMember(); - if (!selected || selected.id === this.root().id) { + if (!selected) { return; } const parentId = parentIdOf(this.root(), selected.id); @@ -243,7 +320,8 @@ export class SharedFilterDialogComponent { return; } this.root.update(root => removeMember(root, selected.id)); - this.selectedId.set(parentId); + const after = this.root(); + this.selectedId.set(parentId === after.id ? (after.members[0]?.id ?? null) : parentId); } // --- Detail-pane edits (immutable, through the root signal) --------------- @@ -451,8 +529,14 @@ export class SharedFilterDialogComponent { this.dialogRef.close(); } - /** Emit the edited tree as an actslib definition (validation gated the button). */ + /** + * Emit the edited tree as an actslib `FilterRoot` (validation gated the + * button). `Simplify` reduces the 1-member wrapper to a bare condition so a + * single-condition filter (case 1) leaves the dialog in its minimal form; + * a group tree (case 2) is returned unchanged. The empty filter (case 0) + * is not submittable — clearing is the pages' Clear Filter button. + */ onSubmit(): void { - this.dialogRef.close({ root: emitTree(this.root(), this.properties) }); + this.dialogRef.close({ root: FilterUtility.Simplify(emitTree(this.root(), this.properties)) }); } } diff --git a/src/app/shared/filter-dialog/index.ts b/src/app/shared/filter-dialog/index.ts index e12b67a..0347744 100644 --- a/src/app/shared/filter-dialog/index.ts +++ b/src/app/shared/filter-dialog/index.ts @@ -3,4 +3,5 @@ // it) plus the schema types; the model functions are exported for pages that // want the summary (menu labels) or round-trip helpers. export * from './filter-dialog-model'; +export * from './filter-dialog-launcher'; export { SharedFilterDialogComponent } from './filter-dialog.component'; diff --git a/src/assets/data/i18n/en.json b/src/assets/data/i18n/en.json index ede750c..6e5fedf 100644 --- a/src/assets/data/i18n/en.json +++ b/src/assets/data/i18n/en.json @@ -69,10 +69,10 @@ "editContentFilter": "Content filter", "editFilter": "Filter", "filterCondition": "Condition", - "filterDetailEmpty": "Select a tree node to edit it", + "filterDetailEmpty": "The filter is empty — use the toolbar above to add a condition or a group", "filterEnumNeedsChoice": "Select at least one value", "filterGroup": "Condition group", - "filterGroupNeedsTwo": "A non-root condition group needs at least two members", + "filterGroupNeedsTwo": "A condition group needs at least two members", "filterJoinHint": "Connects the conditions and groups below", "filterJoinSelect": "Join conditions with", "filterNeedsValue": "Value is required", @@ -108,6 +108,7 @@ "previous": "Previous", "previousQuestion": "Previous Question", "quickSelection": "Selection", + "removeFilterCondition": "Remove condition", "removeFilterGroup": "Remove group", "quit": "Quit", "randomSelect": "Select Randomly", @@ -186,17 +187,6 @@ "directionCn2En": "Chinese -> English", "editWordFilter": "Word filter", "wordOpIsPhrase": "Is phrase", - "filterGroup": "Condition group", - "filterJoinHint": "Connects the conditions and groups below", - "filterJoinSelect": "Join conditions with", - "addFilterGroup": "Add group", - "removeFilterGroup": "Remove group", - "filterGroupNeedsTwo": "A non-root condition group needs at least two members", - "filterCondition": "Condition", - "filterDetailEmpty": "Select a tree node to edit it", - "filterSplitter": "Resize the tree and detail panes", - "filterPreview": "Expression", - "filterPreviewEmpty": "No conditions", "word": "Word", "downloadTemplate": "Download Template", "addTempFile": "Add Temp. File", diff --git a/src/assets/data/i18n/zh-CN.json b/src/assets/data/i18n/zh-CN.json index c7590bd..9746f39 100644 --- a/src/assets/data/i18n/zh-CN.json +++ b/src/assets/data/i18n/zh-CN.json @@ -69,10 +69,10 @@ "editContentFilter": "内容筛选", "editFilter": "筛选", "filterCondition": "条件", - "filterDetailEmpty": "请选择左侧树节点进行编辑", + "filterDetailEmpty": "筛选条件为空,请使用上方工具栏添加条件或条件组", "filterEnumNeedsChoice": "至少选择一个值", "filterGroup": "条件组", - "filterGroupNeedsTwo": "非根条件组至少需要两个成员", + "filterGroupNeedsTwo": "条件组至少需要两个成员", "filterJoinHint": "连接下方的条件与条件组", "filterJoinSelect": "条件连接方式", "filterNeedsValue": "值不能为空", @@ -108,6 +108,7 @@ "previous": "上一个", "previousQuestion": "上一题", "quickSelection": "选择", + "removeFilterCondition": "删除条件", "removeFilterGroup": "删除条件组", "quit": "退出", "randomSelect": "随机选择", @@ -143,17 +144,6 @@ "directionCn2En": "中文 -> 英语", "editWordFilter": "单词筛选", "wordOpIsPhrase": "是词组", - "filterGroup": "条件组", - "filterJoinHint": "连接下方的条件与条件组", - "filterJoinSelect": "条件连接方式", - "addFilterGroup": "添加条件组", - "removeFilterGroup": "删除条件组", - "filterGroupNeedsTwo": "非根条件组至少需要两个成员", - "filterCondition": "条件", - "filterDetailEmpty": "请选择左侧树节点进行编辑", - "filterSplitter": "调整树与详情面板的宽度", - "filterPreview": "表达式", - "filterPreviewEmpty": "暂无条件", "word": "单词", "downloadTemplate": "下载模板", "addTempFile": "添加临时文件", diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 02792c4..8e5c896 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,8 +1,8 @@ export const environment = { homeurl: 'https://www.alvachien.com/learning', production: true, - releasedate: '2026-08-30', - version: '1.9.1', + releasedate: '2026-09-05', + version: '1.9.5', apiUrl: 'https://www.alvachien.com/learningutil', pageTitle: 'Knowledge Builder', loginRequired: true, diff --git a/src/environments/environment.ts b/src/environments/environment.ts index a3f7cbc..f473360 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -6,8 +6,8 @@ export const environment = { homeurl: 'http://localhost:29800', production: false, - releasedate: '2026-08-30', - version: '1.9.1', + releasedate: '2026-09-05', + version: '1.9.5', apiUrl: 'https://localhost:7135', pageTitle: 'Knowledge Builder', loginRequired: true, From 418f27e635d236448991bddb2db9d076e9cad44a Mon Sep 17 00:00:00 2001 From: Alva Chien Date: Sat, 5 Sep 2026 13:52:12 +0800 Subject: [PATCH 3/4] test(audio-service): mock Howl via DI and drop wall-clock awaits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The spec set window.Howl = MockHowl, but AudioService constructs real Howl instances through the root-provided HOWL_FACTORY token — so under jsdom real-howler internals stayed alive across test boundaries, and the two async tests' wall-clock setTimeout waits stalled past the 5s CI timeout under load (flaky on PR #86: same commit passed the push run, failed the pull_request run). Provide HOWL_FACTORY/HOWLER_GLOBAL mocks in TestBed (same pattern as the extended spec) and make both tests synchronous: BehaviorSubject completion is a synchronous notification and the mocked Howl reports 'loaded' immediately, so the awaits only added stall surface. Co-Authored-By: Claude Code --- .../services/audio-service.service.spec.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/app/services/audio-service.service.spec.ts b/src/app/services/audio-service.service.spec.ts index 2c12242..d0cd7f0 100644 --- a/src/app/services/audio-service.service.spec.ts +++ b/src/app/services/audio-service.service.spec.ts @@ -5,6 +5,8 @@ import { TestBed } from '@angular/core/testing'; import { vi } from 'vitest'; import { AudioService } from './audio-service.service'; +import { HOWL_FACTORY } from './howl-factory'; +import { HOWLER_GLOBAL } from './howler.token'; describe('AudioService', () => { let service: AudioService; @@ -68,8 +70,9 @@ describe('AudioService', () => { // Pending onload timer (scheduled below); cleared on unload() so a // superseded/destroyed Howl can't fire onload into the next test and // call .next() on already-completed subjects (which throws and wedges - // the runner under CI load). See "should complete all subjects on - // destroy" / "should handle load and play cycle". + // the runner under CI load). The mock reaches AudioService through + // HOWL_FACTORY below — window.Howl would NOT (the real howler class is + // what the root-provided factory constructs). self._onloadTimer = undefined as ReturnType | undefined; self.unload = vi.fn(() => { if (self._onloadTimer !== undefined) { @@ -118,15 +121,12 @@ describe('AudioService', () => { beforeEach(() => { howlerVolumeSpy = vi.fn(); - (window as any).Howl = MockHowl; - (window as any).Howler = { - volume: howlerVolumeSpy, - }; - TestBed.configureTestingModule({ providers: [ AudioService, - { provide: NgZone, useValue: new NgZone({}) }, + { provide: NgZone, useValue: new NgZone({ enableLongStackTrace: false }) }, + { provide: HOWL_FACTORY, useValue: (config: unknown) => new (MockHowl as any)(config) }, + { provide: HOWLER_GLOBAL, useValue: { volume: howlerVolumeSpy } }, provideHttpClient(withXhr()), provideHttpClientTesting(), ], @@ -135,8 +135,6 @@ describe('AudioService', () => { }); afterEach(() => { - (window as any).Howl = undefined; - (window as any).Howler = undefined; if (service) { service.ngOnDestroy(); } @@ -192,7 +190,11 @@ describe('AudioService', () => { expect(() => service.toggle()).not.toThrow(); }); - it('should complete all subjects on destroy', async () => { + // Both tests below are deliberately synchronous: Subject completion is a + // synchronous notification, and the mocked Howl reports 'loaded' immediately, + // so awaiting wall-clock timers here only added CI flake (the awaits used to + // stall until the 5s test timeout under load). + it('should complete all subjects on destroy', () => { let stateCompleted = false; let positionCompleted = false; let durationCompleted = false; @@ -205,16 +207,14 @@ describe('AudioService', () => { service.ngOnDestroy(); - await new Promise(resolve => setTimeout(resolve, 0)); expect(stateCompleted).toBe(true); expect(positionCompleted).toBe(true); expect(durationCompleted).toBe(true); expect(volumeCompleted).toBe(true); }); - it('should handle load and play cycle', async () => { + it('should handle load and play cycle', () => { void service.load('test.mp3'); - await new Promise(resolve => setTimeout(resolve, 10)); expect(service.currentAudioFile).toBe('test.mp3'); expect(() => service.play()).not.toThrow(); }); From 160e1dc282777c6b38352bd9ee55d3494e38f2d4 Mon Sep 17 00:00:00 2001 From: Alva Chien Date: Sat, 5 Sep 2026 14:00:50 +0800 Subject: [PATCH 4/4] test(navigation-focus): drive 100ms focus timer with fake timers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NavigationFocusService focuses inside a real setTimeout(100) after NavigationEnd, and the tests' whenStable() does not reliably wait for it — under CI worker starvation the focus assertion ran before the timer fired (document.activeElement still ), failing "should focus on component then relinquish focus" on the pull_request run of PR #86. Scope setTimeout/clearTimeout fakes to the two navigation tests and advance the focus timer deterministically — the same pattern (and the same root flake) the navbar specs already adopted. Co-Authored-By: Claude Code --- .../navigation-focus/navigation-focus.spec.ts | 77 ++++++++++++------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/src/app/shared/navigation-focus/navigation-focus.spec.ts b/src/app/shared/navigation-focus/navigation-focus.spec.ts index 4e1d2d0..574b837 100644 --- a/src/app/shared/navigation-focus/navigation-focus.spec.ts +++ b/src/app/shared/navigation-focus/navigation-focus.spec.ts @@ -4,6 +4,7 @@ import type { ComponentFixture } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { provideRouter } from '@angular/router'; +import { vi } from 'vitest'; import { NavigationFocusDirective } from './navigation-focus'; import { NavigationFocusService } from './navigation-focus.service'; @@ -93,38 +94,58 @@ describe('Navigation focus service', () => { expect(navigationFocusService.isNavigationWithinComponentView(previousUrl, newUrl)).toBe(false); }); + // NavigationFocusService focuses via a real 100ms setTimeout after + // NavigationEnd, and whenStable() does not reliably wait for that timer — + // these tests raced the wall clock and failed under CI worker starvation + // (same flake the navbar specs hit). Fake only setTimeout/clearTimeout and + // advance the focus timer deterministically instead. it('should focus on component then relinquish focus', async () => { - const target1 = fixture.nativeElement.querySelector('#target1'); - const target2 = fixture.nativeElement.querySelector('#target2'); - - // First navigation event doesn't trigger focus because it represents a hardnav. - navigationFocusService.requestFocusOnNavigation(target1); - navigationFocusService.requestFocusOnNavigation(target2); - await navigate('/'); - expect(document.activeElement).not.toEqual(target1); - expect(document.activeElement).not.toEqual(target2); - - // Most recent requester gets focus on the next nav. - await navigate('/guides'); - expect(document.activeElement).toEqual(target2); - - // Falls back to the focusing the previous requester once the most recent one relinquishes. - navigationFocusService.relinquishFocusOnNavigation(target2); - await navigate('/cdk'); - expect(document.activeElement).toEqual(target1); + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }); + try { + const target1 = fixture.nativeElement.querySelector('#target1'); + const target2 = fixture.nativeElement.querySelector('#target2'); + + // First navigation event doesn't trigger focus because it represents a hardnav. + navigationFocusService.requestFocusOnNavigation(target1); + navigationFocusService.requestFocusOnNavigation(target2); + await navigate('/'); + vi.advanceTimersByTime(150); + expect(document.activeElement).not.toEqual(target1); + expect(document.activeElement).not.toEqual(target2); + + // Most recent requester gets focus on the next nav. + await navigate('/guides'); + vi.advanceTimersByTime(150); + expect(document.activeElement).toEqual(target2); + + // Falls back to the focusing the previous requester once the most recent one relinquishes. + navigationFocusService.relinquishFocusOnNavigation(target2); + await navigate('/cdk'); + vi.advanceTimersByTime(150); + expect(document.activeElement).toEqual(target1); + } finally { + vi.useRealTimers(); + } }); it('should not set focus when navigating to hash target', async () => { - const target1 = fixture.nativeElement.querySelector('#target1'); - - // First navigation event doesn't trigger focus because it represents a hardnav. - navigationFocusService.requestFocusOnNavigation(target1); - await navigate('/'); - expect(document.activeElement).not.toEqual(target1); - - // Navigating to a hash target should not set focus on target1 even though it requested focus - await navigate('/guides#hash'); - expect(document.activeElement).not.toEqual(target1); + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }); + try { + const target1 = fixture.nativeElement.querySelector('#target1'); + + // First navigation event doesn't trigger focus because it represents a hardnav. + navigationFocusService.requestFocusOnNavigation(target1); + await navigate('/'); + vi.advanceTimersByTime(150); + expect(document.activeElement).not.toEqual(target1); + + // Navigating to a hash target should not set focus on target1 even though it requested focus + await navigate('/guides#hash'); + vi.advanceTimersByTime(150); + expect(document.activeElement).not.toEqual(target1); + } finally { + vi.useRealTimers(); + } }); });