From 873b9b218d4a2f4b7e71c7ce659d625141c18d70 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Fri, 24 Jul 2026 18:04:21 +0200 Subject: [PATCH] feat: publish Regex Tools 0.1.0 --- .gitignore | 12 + .npmrc | 2 + CHANGELOG.md | 12 + LICENSE | 232 + LICENSES/CodeMirror-MIT.txt | 21 + LICENSES/Lezer-MIT.txt | 21 + LICENSES/React-MIT.txt | 21 + LICENSES/fflate-MIT.txt | 21 + LICENSES/regexpp-MIT.txt | 21 + LICENSES/toolbox-sdk-Apache-2.0.txt | 192 + README.md | 242 + SOURCE.md | 23 + THIRD_PARTY_NOTICES.md | 29 + docs/ARCHITECTURE.md | 41 + docs/ENGINE_BUILDS.md | 28 + docs/EXECUTION_TRACES.md | 13 + docs/EXPLANATION_MODEL.md | 20 + docs/EXTRACTION_MODEL.md | 27 + docs/FLAVOUR_SUPPORT.md | 20 + docs/PARSER_PROFILES.md | 16 + docs/PERFORMANCE.md | 66 + docs/PORTAL_REQUIREMENTS.md | 19 + docs/REFERENCE_IMPLEMENTATIONS.md | 21 + docs/RELEASE.md | 23 + docs/SECURITY.md | 51 + eslint.config.mjs | 43 + index.html | 17 + package-lock.json | 4166 +++++++++++++++++ package.json | 81 + playwright.config.ts | 25 + public/engines/README.md | 7 + public/favicon.svg | 7 + public/toolbox-app.json | 40 + scripts/build-engines.mjs | 24 + scripts/checksum-release.mjs | 12 + scripts/generate-toolbox-manifest.mjs | 42 + scripts/package-release.mjs | 526 +++ scripts/package-release.test.ts | 51 + scripts/serve-test.mjs | 79 + scripts/verify-engine-assets.mjs | 34 + src/App.tsx | 42 + src/app/AppErrorBoundary.tsx | 37 + src/browser/clipboard.test.ts | 52 + src/browser/clipboard.ts | 34 + src/components/CapabilityPanel.test.tsx | 88 + src/components/CapabilityPanel.tsx | 122 + src/components/CaptureTable.test.tsx | 236 + src/components/CaptureTable.tsx | 551 +++ src/components/DiagnosticsPanel.tsx | 42 + src/components/ExplanationTree.test.tsx | 94 + src/components/ExplanationTree.tsx | 229 + src/components/ExtractionTree.test.tsx | 76 + src/components/ExtractionTree.tsx | 223 + src/components/HelpDialog.tsx | 151 + src/components/ListPanel.tsx | 226 + src/components/ProjectPanel.tsx | 192 + src/components/QuickReference.test.tsx | 26 + src/components/QuickReference.tsx | 94 + .../ReplacementPanel.completeness.test.tsx | 89 + src/components/ReplacementPanel.tsx | 284 ++ .../ReplacementPreviewPanel.test.tsx | 72 + src/components/ReplacementPreviewPanel.tsx | 166 + src/components/TemplateLimits.test.tsx | 172 + src/components/UnitTestPanel.test.tsx | 573 +++ src/components/UnitTestPanel.tsx | 1106 +++++ src/components/Workbench.test.tsx | 383 ++ src/components/Workbench.tsx | 1809 +++++++ src/components/tree-navigation.ts | 65 + src/editors/CodeEditor.tsx | 320 ++ src/editors/editor.types.ts | 9 + src/main.tsx | 9 + src/project/persistence.test.ts | 147 + src/project/persistence.ts | 97 + src/project/project-limits.test.ts | 42 + src/project/project-limits.ts | 219 + src/project/project.serialization.ts | 50 + src/project/project.test.ts | 246 + src/project/project.types.ts | 37 + src/project/project.validation.ts | 398 ++ src/regex/execution/EngineSupervisor.ts | 73 + src/regex/execution/RegexEngineAdapter.ts | 15 + src/regex/execution/SyntaxSupervisor.ts | 71 + src/regex/execution/WorkerSupervisor.test.ts | 125 + src/regex/execution/WorkerSupervisor.ts | 223 + .../EcmaScriptEngineAdapter.test.ts | 412 ++ .../ecmascript/EcmaScriptEngineAdapter.ts | 556 +++ .../execution/offsets/codepoint-to-utf16.ts | 49 + .../execution/offsets/line-offset-map.test.ts | 32 + .../execution/offsets/line-offset-map.ts | 49 + .../execution/offsets/offset-map.test.ts | 66 + src/regex/execution/offsets/offset-map.ts | 57 + src/regex/execution/offsets/utf8-to-utf16.ts | 62 + src/regex/execution/request-limits.test.ts | 12 + src/regex/execution/request-limits.ts | 84 + src/regex/execution/worker-protocol.ts | 89 + src/regex/extraction/build-extraction-tree.ts | 121 + src/regex/extraction/capture-rows.ts | 63 + src/regex/extraction/extraction.test.ts | 63 + src/regex/list/list-template.test.ts | 257 + src/regex/list/list-template.ts | 286 ++ src/regex/model/diagnostics.ts | 14 + src/regex/model/flavour.ts | 45 + src/regex/model/match.ts | 99 + src/regex/model/syntax.ts | 157 + src/regex/reference/token-reference.ts | 142 + .../replacement/replacement-preview.test.ts | 342 ++ src/regex/replacement/replacement-preview.ts | 303 ++ src/regex/syntax/SyntaxProvider.ts | 25 + .../syntax/pattern-syntax-freshness.test.ts | 36 + src/regex/syntax/pattern-syntax-freshness.ts | 31 + .../EcmaScriptSyntaxProvider.test.ts | 280 ++ .../ecmascript/EcmaScriptSyntaxProvider.ts | 119 + .../syntax/providers/ecmascript/normalize.ts | 678 +++ .../providers/ecmascript/replacement.ts | 247 + src/regex/tests/current-result-draft.test.ts | 55 + src/regex/tests/current-result-draft.ts | 45 + src/regex/tests/test-case.types.ts | 57 + src/regex/tests/test-runner.test.ts | 371 ++ src/regex/tests/test-runner.ts | 269 ++ .../tests/test-suite.serialization.test.ts | 88 + src/regex/tests/test-suite.serialization.ts | 108 + src/styles.css | 1497 ++++++ src/test/setup.ts | 5 + src/toolbox/manifest.source.json | 40 + src/toolbox/manifest.ts | 4 + src/types/scripts.d.ts | 13 + src/version.ts | 3 + src/workers/ecmascript.worker.ts | 51 + src/workers/syntax.worker.ts | 49 + tests/browser/workbench.spec.ts | 781 +++ .../ecmascript.conformance.test.ts | 248 + tests/fixtures/README.md | 13 + tsconfig.app.json | 27 + tsconfig.json | 7 + tsconfig.node.json | 24 + vite.config.ts | 18 + 136 files changed, 24212 insertions(+) create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 LICENSES/CodeMirror-MIT.txt create mode 100644 LICENSES/Lezer-MIT.txt create mode 100644 LICENSES/React-MIT.txt create mode 100644 LICENSES/fflate-MIT.txt create mode 100644 LICENSES/regexpp-MIT.txt create mode 100644 LICENSES/toolbox-sdk-Apache-2.0.txt create mode 100644 README.md create mode 100644 SOURCE.md create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/ENGINE_BUILDS.md create mode 100644 docs/EXECUTION_TRACES.md create mode 100644 docs/EXPLANATION_MODEL.md create mode 100644 docs/EXTRACTION_MODEL.md create mode 100644 docs/FLAVOUR_SUPPORT.md create mode 100644 docs/PARSER_PROFILES.md create mode 100644 docs/PERFORMANCE.md create mode 100644 docs/PORTAL_REQUIREMENTS.md create mode 100644 docs/REFERENCE_IMPLEMENTATIONS.md create mode 100644 docs/RELEASE.md create mode 100644 docs/SECURITY.md create mode 100644 eslint.config.mjs create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 playwright.config.ts create mode 100644 public/engines/README.md create mode 100644 public/favicon.svg create mode 100644 public/toolbox-app.json create mode 100644 scripts/build-engines.mjs create mode 100644 scripts/checksum-release.mjs create mode 100644 scripts/generate-toolbox-manifest.mjs create mode 100644 scripts/package-release.mjs create mode 100644 scripts/package-release.test.ts create mode 100644 scripts/serve-test.mjs create mode 100644 scripts/verify-engine-assets.mjs create mode 100644 src/App.tsx create mode 100644 src/app/AppErrorBoundary.tsx create mode 100644 src/browser/clipboard.test.ts create mode 100644 src/browser/clipboard.ts create mode 100644 src/components/CapabilityPanel.test.tsx create mode 100644 src/components/CapabilityPanel.tsx create mode 100644 src/components/CaptureTable.test.tsx create mode 100644 src/components/CaptureTable.tsx create mode 100644 src/components/DiagnosticsPanel.tsx create mode 100644 src/components/ExplanationTree.test.tsx create mode 100644 src/components/ExplanationTree.tsx create mode 100644 src/components/ExtractionTree.test.tsx create mode 100644 src/components/ExtractionTree.tsx create mode 100644 src/components/HelpDialog.tsx create mode 100644 src/components/ListPanel.tsx create mode 100644 src/components/ProjectPanel.tsx create mode 100644 src/components/QuickReference.test.tsx create mode 100644 src/components/QuickReference.tsx create mode 100644 src/components/ReplacementPanel.completeness.test.tsx create mode 100644 src/components/ReplacementPanel.tsx create mode 100644 src/components/ReplacementPreviewPanel.test.tsx create mode 100644 src/components/ReplacementPreviewPanel.tsx create mode 100644 src/components/TemplateLimits.test.tsx create mode 100644 src/components/UnitTestPanel.test.tsx create mode 100644 src/components/UnitTestPanel.tsx create mode 100644 src/components/Workbench.test.tsx create mode 100644 src/components/Workbench.tsx create mode 100644 src/components/tree-navigation.ts create mode 100644 src/editors/CodeEditor.tsx create mode 100644 src/editors/editor.types.ts create mode 100644 src/main.tsx create mode 100644 src/project/persistence.test.ts create mode 100644 src/project/persistence.ts create mode 100644 src/project/project-limits.test.ts create mode 100644 src/project/project-limits.ts create mode 100644 src/project/project.serialization.ts create mode 100644 src/project/project.test.ts create mode 100644 src/project/project.types.ts create mode 100644 src/project/project.validation.ts create mode 100644 src/regex/execution/EngineSupervisor.ts create mode 100644 src/regex/execution/RegexEngineAdapter.ts create mode 100644 src/regex/execution/SyntaxSupervisor.ts create mode 100644 src/regex/execution/WorkerSupervisor.test.ts create mode 100644 src/regex/execution/WorkerSupervisor.ts create mode 100644 src/regex/execution/adapters/ecmascript/EcmaScriptEngineAdapter.test.ts create mode 100644 src/regex/execution/adapters/ecmascript/EcmaScriptEngineAdapter.ts create mode 100644 src/regex/execution/offsets/codepoint-to-utf16.ts create mode 100644 src/regex/execution/offsets/line-offset-map.test.ts create mode 100644 src/regex/execution/offsets/line-offset-map.ts create mode 100644 src/regex/execution/offsets/offset-map.test.ts create mode 100644 src/regex/execution/offsets/offset-map.ts create mode 100644 src/regex/execution/offsets/utf8-to-utf16.ts create mode 100644 src/regex/execution/request-limits.test.ts create mode 100644 src/regex/execution/request-limits.ts create mode 100644 src/regex/execution/worker-protocol.ts create mode 100644 src/regex/extraction/build-extraction-tree.ts create mode 100644 src/regex/extraction/capture-rows.ts create mode 100644 src/regex/extraction/extraction.test.ts create mode 100644 src/regex/list/list-template.test.ts create mode 100644 src/regex/list/list-template.ts create mode 100644 src/regex/model/diagnostics.ts create mode 100644 src/regex/model/flavour.ts create mode 100644 src/regex/model/match.ts create mode 100644 src/regex/model/syntax.ts create mode 100644 src/regex/reference/token-reference.ts create mode 100644 src/regex/replacement/replacement-preview.test.ts create mode 100644 src/regex/replacement/replacement-preview.ts create mode 100644 src/regex/syntax/SyntaxProvider.ts create mode 100644 src/regex/syntax/pattern-syntax-freshness.test.ts create mode 100644 src/regex/syntax/pattern-syntax-freshness.ts create mode 100644 src/regex/syntax/providers/ecmascript/EcmaScriptSyntaxProvider.test.ts create mode 100644 src/regex/syntax/providers/ecmascript/EcmaScriptSyntaxProvider.ts create mode 100644 src/regex/syntax/providers/ecmascript/normalize.ts create mode 100644 src/regex/syntax/providers/ecmascript/replacement.ts create mode 100644 src/regex/tests/current-result-draft.test.ts create mode 100644 src/regex/tests/current-result-draft.ts create mode 100644 src/regex/tests/test-case.types.ts create mode 100644 src/regex/tests/test-runner.test.ts create mode 100644 src/regex/tests/test-runner.ts create mode 100644 src/regex/tests/test-suite.serialization.test.ts create mode 100644 src/regex/tests/test-suite.serialization.ts create mode 100644 src/styles.css create mode 100644 src/test/setup.ts create mode 100644 src/toolbox/manifest.source.json create mode 100644 src/toolbox/manifest.ts create mode 100644 src/types/scripts.d.ts create mode 100644 src/version.ts create mode 100644 src/workers/ecmascript.worker.ts create mode 100644 src/workers/syntax.worker.ts create mode 100644 tests/browser/workbench.spec.ts create mode 100644 tests/conformance/ecmascript.conformance.test.ts create mode 100644 tests/fixtures/README.md create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b88b9b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +node_modules/ +dist/ +release/ +coverage/ +playwright-report/ +test-results/ +*.tsbuildinfo +.DS_Store +.env +.env.* +!.env.example + diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..56724c0 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +@add-ideas:registry=https://git.add-ideas.de/api/packages/zemion/npm/ + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a468414 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +## 0.1.0 — 2026-07-24 + +- Added the open-source ECMAScript 2025 syntax provider using regexpp 4.12.2. +- Added native browser `RegExp` execution in a killable, recoverable worker. +- Added deterministic explanation and actual extraction trees. +- Added exact capture indices, synchronized highlighting and bounded captures. +- Added replacement, list/extraction and unit-test workspaces. +- Added project import/export, optional local persistence and privacy controls. +- Added resource limits, real timeout termination and recovery tests. +- Added Toolbox SDK integration and deterministic release packaging. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f6cdd22 --- /dev/null +++ b/LICENSE @@ -0,0 +1,232 @@ +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and other kinds of works. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS + +0. Definitions. + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on the Program. + +To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. + +A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. +A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Use with the GNU Affero General Public License. +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. + +14. Revised Versions of this License. +The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . + +The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . diff --git a/LICENSES/CodeMirror-MIT.txt b/LICENSES/CodeMirror-MIT.txt new file mode 100644 index 0000000..9a91f48 --- /dev/null +++ b/LICENSES/CodeMirror-MIT.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (C) 2018-2021 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/LICENSES/Lezer-MIT.txt b/LICENSES/Lezer-MIT.txt new file mode 100644 index 0000000..c21df7e --- /dev/null +++ b/LICENSES/Lezer-MIT.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (C) 2018 by Marijn Haverbeke and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/LICENSES/React-MIT.txt b/LICENSES/React-MIT.txt new file mode 100644 index 0000000..b93be90 --- /dev/null +++ b/LICENSES/React-MIT.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSES/fflate-MIT.txt b/LICENSES/fflate-MIT.txt new file mode 100644 index 0000000..33ba32d --- /dev/null +++ b/LICENSES/fflate-MIT.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Arjun Barrett + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSES/regexpp-MIT.txt b/LICENSES/regexpp-MIT.txt new file mode 100644 index 0000000..883ee1f --- /dev/null +++ b/LICENSES/regexpp-MIT.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Toru Nagashima + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSES/toolbox-sdk-Apache-2.0.txt b/LICENSES/toolbox-sdk-Apache-2.0.txt new file mode 100644 index 0000000..c38585f --- /dev/null +++ b/LICENSES/toolbox-sdk-Apache-2.0.txt @@ -0,0 +1,192 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + Copyright 2026 ADD Ideas + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0274a00 --- /dev/null +++ b/README.md @@ -0,0 +1,242 @@ +# Regex Tools + +Regex Tools is a production-oriented, local-first browser workbench for +developing, understanding, testing and applying regular expressions. It is an +independent static React/Vite application and a module of add·ideas Toolbox. + +Patterns, replacement templates, test text and project files stay in the +browser. There is no account, backend, upload, remote execution, telemetry, +runtime CDN, remote font, or cloud-save service. + +## Current milestone + +Version 0.1.0 is the first production flavour slice: ECMAScript. + +- Syntax acceptance: `@eslint-community/regexpp` 4.12.2, ECMAScript 2025 + grammar; application-owned structural explanations are partial and + feature-matrix tested. +- Execution engine: the current browser's native ECMAScript `RegExp`. +- Native and editor offsets: UTF-16 code units. +- Parsing and execution run in separate workers. +- Actual execution can be terminated by killing its worker. +- The worker is recreated after timeout, crash or cancellation. +- Capture ranges use the engine's `d` indices result. +- Named, numbered, optional, empty and repeated-final captures are represented. +- Match, replacement, typed list/extraction and unit-test modes are available. +- Project JSON import/export and optional IndexedDB save are available. +- Interactive test text is not persisted by default. + +No licensed parser is used, referenced at runtime, or required to build the +project. This is the community build and will grow flavour by flavour using +reviewed open-source syntax providers and actual named engines. + +## The two trees + +The **explanation tree** is a deterministic structural explanation generated +from the application-owned normalized syntax tree. It is not an engine trace. + +The **extraction tree** contains actual engine matches and captures. Its +hierarchy uses syntactic capture nesting while preserving actual engine ranges. +ECMAScript exposes only the final retained capture of a repeated group; Regex +Tools says so and never invents capture history. + +Browser ECMAScript engines do not expose their internal backtracking trace. +Version 0.1.0 therefore presents no ECMAScript execution trace and no +educational trace disguised as one. + +## Workbench modes + +- **Match** — live highlighting, extraction tree and bounded capture table. +- **Replace** — native ECMAScript `RegExp` matches with bounded, + specification-compatible string substitution; output completeness is + reported separately from bounded per-match original/result, changed-range and + token-contribution previews that synchronize the replacement, pattern, + subject and output editors. +- **List** — non-executable typed templates with text, CSV, JSON and NDJSON + export; export is disabled when engine collection or bounded rendering is + incomplete. +- **Unit tests** — editable and cloneable versioned presence, count, full-match, + capture, replacement, timing and expected-timeout assertions. Capture a + complete current result, select only cases to rerun, filter failures, and + import/export a standalone validated JSON suite; execution stays in isolated + workers. + +Corpus, comparison, debugging, analysis, generation, minimization, formatting +and code generation remain roadmap items. Controls for them are not exposed in +this release. + +## Flags and iteration + +Pattern and flags are stored separately; delimiters are presentation only. +Native `g` and `y` behavior is preserved. The optional **Scan all** action is +explicit. When neither `g` nor `y` is present it adds `g` only to that execution +request, visibly records the internal flag, and does not change the saved user +flags. The `d` flag may likewise be added internally to obtain exact indices +without changing matching semantics. + +Zero-length global iteration advances using the selected Unicode semantics and +cannot loop forever. + +## Limits and timeout behavior + +Important defaults: + +| Limit | Default | +| ------------------------------ | -----------------: | +| Live parse debounce | 120 ms | +| Live execution debounce | 220 ms | +| Live execution timeout | 250 ms | +| Manual execution timeout | 2 s | +| Advanced maximum timeout | 10 s | +| Pattern hard limit | 64 Ki UTF-16 units | +| Interactive subject hard limit | 16 MiB | +| Replacement template | 64 Ki UTF-16 units | +| List template | 16 Ki UTF-16 units | +| Per-match replacement preview | 200 matches | +| Replacement contributions | 2,000 | +| Maximum matches | 10,000 | +| Maximum capture groups | 1,000 | +| Maximum capture rows | 100,000 | +| Replacement preview | 64 MiB | +| Project JSON document | 32 MiB UTF-8 | +| Standalone test-suite JSON | 32 MiB UTF-8 | +| In-memory unit-test suite | 32 MiB UTF-8 | +| Tests per suite | 1,000 | +| Retained message per test | 2 KiB UTF-8 | +| Unit-test suite wall time | 60 s | + +A timeout terminates actual execution. It is reported as **timed out**, never as +“no match”. The next request creates a fresh worker. + +Replacement applies exactly the authoritative matches returned under the match +and capture-row limits. If either collection limit is reached, later subject +text remains unchanged and the output is explicitly reported as partial. The +separate output-byte limit reports a truncated output preview. + +## Project persistence + +Project schema version 1 stores flavour, pattern, flags, options, replacement, +list template, unit tests (including explicit scan-all behavior and supported +per-test timeouts) and selected UI state. Import rebuilds a validated plain +object; it never evaluates code and pauses live execution until review. +Workbench-wide manual timeout changes are not persisted, and unsupported +project resource overrides are rejected. + +Interactive test text is excluded from local save and export unless explicitly +enabled. Unit-test definitions are deliberate project data, but their subjects +require separate explicit opt-ins for JSON export and IndexedDB save. +Standalone test-suite JSON likewise omits subjects by default, is schema- and +field-validated on import, and appends at most 1,000 total tests. Add, update, +clone and append-import also preflight the complete in-memory suite against a +32 MiB aggregate bound. Corpus content is not supported or persisted in this +release. Import, export and IndexedDB save enforce one 32 MiB aggregate UTF-8 +JSON-document limit in addition to per-field limits. The latest local save is +read through an `updatedAt` index cursor; loading it does not materialize the +complete project store. + +## Development + +Requirements: Node.js 22 or later and npm 11. + +```sh +npm install +npm run dev +``` + +Quality and release commands: + +```sh +npm run typecheck +npm run lint +npm run format:check +npm test +npm run test:conformance +npm run test:browser +npm run engines:build +npm run engines:verify +npm run build +npm run toolbox:check +npm run check +npm run release:artifact +``` + +The normal build performs no network download. `vite.config.ts` uses +`base: "./"`; all workers and assets are self-hosted and nested-path safe. + +## Toolbox SDK and Portal + +The application uses Toolbox SDK 0.2.2 `AppShell`. Its canonical typed/static +manifest source is `src/toolbox/manifest.source.json`; generation fails if its +version or source identity drifts. + +The deterministic release ZIP can be consumed by Toolbox Portal using an exact +artifact URL, manifest ID, version and SHA-256. Portal builds consume the +artifact; they do not build Regex Tools source. + +Version 0.1.0 has no WebAssembly. Its minimum CSP needs self-hosted scripts and +workers, not `wasm-unsafe-eval`. See +[`docs/PORTAL_REQUIREMENTS.md`](docs/PORTAL_REQUIREMENTS.md). + +## Flavour roadmap + +The next flavour is official PCRE2 10.47 WebAssembly. A technical spike from the +signed official tag has already demonstrated Unicode matching, named captures, +substitution, automatic and explicit callouts, and match/depth/heap limits. +PCRE2 is not shipped until the application-owned bridge, byte/UTF-16 offsets, +trace caps, browser tests and source/licence material meet the acceptance gate. + +Python, Go, Rust, .NET, Java and legacy PCRE follow only through their actual +named runtimes. No flavour is emulated through JavaScript and relabelled. + +## Licensing + +Regex Tools original source is GPL-3.0-or-later, copyright © 2026 Albrecht +Degering. Shipped dependencies retain their own compatible licences and +notices; see [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) and +[`LICENSES/`](LICENSES/). + +No regex101 application source, branding, generated explanation prose, +reference text or visual design is copied. RegexLib and RGXP.RU fixtures are not +redistributed because a suitable fixture licence was not established. No +RegexHub fixture is shipped in v0.1.0. + +## Known limitations + +- ECMAScript syntax coverage follows regexpp's ECMAScript 2025 grammar; a later + browser proposal may execute before the provider understands it. +- Exact consumed-length bounds for ECMAScript Unicode properties of strings + remain unknown and are reported as such rather than guessed. +- regexpp does not provide tolerant AST recovery. Malformed input receives an + exact reported error position as a zero-width range and an application-owned + error node, not a fabricated multi-character span or partial provider AST. +- Native JavaScript compile errors do not consistently expose a machine-readable + source offset; the provider diagnostic supplies the range. +- Capture history and actual ECMAScript execution tracing are unavailable. +- Capture-table rendering is paged at 200 rows while aggregate collection is + bounded separately. +- Explanation/extraction trees and editor decorations show bounded 2,000-node + previews with explicit totals; normalized results and table/export data stay + separate from those render-only caps. +- Extraction-tree values show at most 100 UTF-16 units and capture-table cells + at most 512. UI-only shortening shows the exact range length; engine-clipped + values are labelled as prefixes of that range. Copy and eligible JSON export + continue to use the separately bounded engine value. +- Replacement parsing is complete below its 64 Ki input limit, while editor + marks, token rows and diagnostics have explicit presentation caps. List token + chips are similarly bounded, and list rendering stops at 250,000 token + evaluations. +- Per-match replacement presentation renders at most 200 matches and 2,000 + token contributions under a 50,000 token-evaluation budget. Bounded + replacement output remains available separately and is labelled partial if + match collection reached a limit. +- Result-value previews are capped per value and overall. List export is + disabled whenever a bounded preview would otherwise be mistaken for complete + data. +- Unit-test failure messages retain at most 2 KiB UTF-8 per case, so 1,000 + results stay below the 2 MiB log budget. Oversized exact replacement results + offer a lightweight `should-match` draft when one is valid and fits the suite. +- Benchmark, risk analysis, generated cases, minimization and corpus processing + are not in this release. + +Architecture, security, performance, provenance and flavour details live in +[`docs/`](docs/). diff --git a/SOURCE.md b/SOURCE.md new file mode 100644 index 0000000..4be6470 --- /dev/null +++ b/SOURCE.md @@ -0,0 +1,23 @@ +# Source identity + +- Project: Regex Tools +- Version: 0.1.0 +- Repository: +- Release tag: `v0.1.0` +- Licence: GPL-3.0-or-later +- Copyright: © 2026 Albrecht Degering + +The release ZIP is generated from the tagged repository with: + +```sh +npm ci +npm run release:artifact +``` + +The build uses only pinned npm packages from `package-lock.json`. It performs no +runtime or build-time CDN fetch. Complete preferred-form source is the tagged +repository. The release archive contains compiled static assets plus legal, +source-identity and attribution documents. + +No PCRE2, Python, Go, Rust, .NET, Java or legacy-PCRE runtime is included in +version 0.1.0. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..c8a8f39 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,29 @@ +# Third-party notices + +Regex Tools original source is GPL-3.0-or-later. The production browser bundle +also contains the compatible components below. Exact dependency resolution is +recorded in `package-lock.json`. + +| Component | Version | Licence | Shipped | Role and source | +| -------------------------------- | ------------------------------------------------------------------------ | ---------- | ----------------- | ------------------------------------------------------------------------ | +| `@add-ideas/toolbox-contract` | 0.2.2 | Apache-2.0 | Runtime | Manifest contract; | +| `@add-ideas/toolbox-shell-react` | 0.2.2 | Apache-2.0 | Runtime | Shared shell; same source | +| `@eslint-community/regexpp` | 4.12.2 | MIT | Syntax worker | ECMAScript parser; | +| CodeMirror packages | state 6.7.1; view 6.43.6; language 6.12.4; commands 6.10.4; search 6.7.1 | MIT | Runtime | Editors; | +| `@lezer/highlight` | 1.2.3 | MIT | Runtime | Editor highlighting support; | +| React / React DOM | 19.2.7 | MIT | Runtime | User interface; | +| `fflate` | 0.8.3 | MIT | Packaging utility | Deterministic release ZIP; | +| Vite | 8.1.5 | MIT | Generated helpers | Production build; | +| Rolldown | 1.1.5 | MIT | Generated helpers | Production bundling; | + +`@add-ideas/toolbox-testkit` 0.2.2 and the other test/build dependencies are +development-only and are not part of the static runtime bundle. + +`recheck` 4.5.0 and `regexp-ast-analysis` 0.7.1 were inspected but deliberately +not installed or shipped. PCRE2 10.47 was used only in an external feasibility +spike and is not present in the v0.1.0 artifact. + +Corresponding licence texts and copyright notices are in `LICENSES/`. The +release package additionally carries the exact Vite and Rolldown legal files +under `LICENSES/build/`, including Vite's bundled-dependency notices for code +emitted by the production build. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..3d49116 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,41 @@ +# Architecture + +Regex Tools is a static browser application with framework-neutral regex +boundaries. + +```text +React workbench + ├─ Pattern SyntaxSupervisor → syntax.worker → Regexpp provider → normalized AST + ├─ Replacement SyntaxSupervisor → syntax.worker → typed replacement tokens + ├─ EngineSupervisor → ecmascript.worker → native RegExp → match DTOs + ├─ Test SyntaxSupervisor + EngineSupervisor → isolated cancellable test runs + └─ project validator / serializer / IndexedDB persistence +``` + +Provider AST objects never cross the worker boundary. The syntax worker emits +application-owned nodes, tokens, diagnostics, capture definitions and explicit +provenance. React never imports or traverses regexpp's cyclic AST. + +Every engine request carries a protocol version, monotonically increasing +request ID and worker generation. A supervisor allows one active request, +rejects stale responses, terminates on timeout/crash/cancel, and lazily creates +a new generation. Timeout is a distinct error state. + +Pattern and replacement syntax use independent supervisors. A pattern snapshot +is bound to its exact pattern, ordered flags and revision; execution cannot use +acceptance or capture metadata from an earlier input. Replacement output is +built while the native engine produces the same bounded authoritative match +sequence that is returned to the UI. + +Per-match replacement ranges and token contributions are deterministic mappings +over those actual matches, the complete replacement-token model and the bounded +output. They execute no replacement callback or user code and have separate +presentation and token-evaluation limits. + +Native ranges are retained in the engine's declared unit. Browser editor ranges +are half-open UTF-16 code-unit ranges. Reusable converters cover UTF-8 byte and +Unicode code-point offsets for later flavours, including invalid-boundary +rejection and lone-surrogate detection. + +The release boundary is `dist/` plus checked-in legal/source documents. The +Portal consumes the resulting immutable ZIP and never imports React source. diff --git a/docs/ENGINE_BUILDS.md b/docs/ENGINE_BUILDS.md new file mode 100644 index 0000000..ec068a7 --- /dev/null +++ b/docs/ENGINE_BUILDS.md @@ -0,0 +1,28 @@ +# Engine builds + +Version 0.1.0 ships no WebAssembly engine. + +`npm run engines:build` verifies that this release has no external pack to +build. After the production build, `npm run engines:verify` rejects any +undeclared engine asset and confirms the packaged ECMAScript-only notice. +Neither command accesses the network. + +## PCRE2 feasibility record + +The next flavour was spiked outside the application repository against official +PCRE2 10.47: + +- signed tag object `cd007b4466798f66d479d1442a407099e7c40050`; +- peeled commit `f454e231fe5006dd7ff8f4693fd2b8eb94333429`; +- licence `BSD-3-Clause WITH PCRE2-exception`; +- Emscripten 6.0.4; +- 8-bit library, Unicode enabled, JIT disabled. + +Node and Chromium tests demonstrated version/config reporting, Unicode, +named groups, global substitution, automatic and explicit callouts, and +match/depth/heap errors. This was a technical spike only. No spike source, +binary or PCRE2 source is included in v0.1.0. + +Production PCRE2 support remains gated on a reviewed bridge, copied DTOs, +allocation cleanup, exact source build, UTF-8/UTF-16 mapping, trace/output caps, +worker recovery, deterministic assets, licences and browser conformance. diff --git a/docs/EXECUTION_TRACES.md b/docs/EXECUTION_TRACES.md new file mode 100644 index 0000000..236ac8f --- /dev/null +++ b/docs/EXECUTION_TRACES.md @@ -0,0 +1,13 @@ +# Execution traces + +Version 0.1.0 exposes no execution trace. + +Browser ECMAScript APIs return compilation, match, capture and replacement +results but not the runtime's internal backtracking events. Regex Tools does +not label structural explanations, static hints or adjacent-result inference +as an actual engine trace. + +The next PCRE2 slice will use actual automatic/explicit callout events from an +application-owned bridge. Event count, serialized bytes, match/depth/heap +limits and wall-clock termination must all be enforced. Classifications such +as “apparent backtrack” will remain visibly derived from adjacent events. diff --git a/docs/EXPLANATION_MODEL.md b/docs/EXPLANATION_MODEL.md new file mode 100644 index 0000000..37f2e16 --- /dev/null +++ b/docs/EXPLANATION_MODEL.md @@ -0,0 +1,20 @@ +# Explanation model + +The community provider converts regexpp nodes into a stable application AST +inside the syntax worker. Nodes carry deterministic IDs, UTF-16 ranges, raw +source, capture metadata, quantifier bounds, assertion kind, support status, +provider identity and provenance. + +Explanations are original deterministic text selected by node type. Nullable +and minimum/maximum consumed-length properties are derived recursively where +the normalized node semantics support them. Backreference lengths and exact +bounds for Unicode properties of strings remain unknown rather than being +guessed. + +regexpp 4.12.2 does not expose tolerant recovery. A malformed pattern therefore +gets the provider's exact diagnostic plus an application-owned error node. No +partial regexpp AST is claimed. + +The explanation tree is structural. It is never called an actual execution +trace. Selecting a node selects and scrolls its exact editor range; selecting +pattern text chooses the smallest enclosing node. diff --git a/docs/EXTRACTION_MODEL.md b/docs/EXTRACTION_MODEL.md new file mode 100644 index 0000000..f1fd021 --- /dev/null +++ b/docs/EXTRACTION_MODEL.md @@ -0,0 +1,27 @@ +# Extraction model + +Extraction nodes contain actual engine match/capture values and ranges. The +syntax tree supplies capture names and parent relationships; engine results +supply participation, value and offsets. + +Statuses distinguish: + +- participated with non-empty text; +- participated and matched empty; +- did not participate; +- unavailable range; +- preview-truncated value. + +Exact match and capture ranges are retained even when a displayed value exceeds +the 64 Ki UTF-16 per-value preview limit or the shared 8 Mi-unit preview +budget. Consumers must inspect the preview status rather than treating a +preview as complete. In particular, list export is disabled if its template +would consume an incomplete value. + +Repeated ECMAScript captures expose only the final retained capture. The UI +labels this limitation and does not invent history. Later .NET support may add +actual engine capture-history nodes without degrading them to this model. + +The syntactic hierarchy remains useful even if actual spans overlap or fall +outside a parent's geometry; native and normalized ranges are preserved rather +than “fixed”. diff --git a/docs/FLAVOUR_SUPPORT.md b/docs/FLAVOUR_SUPPORT.md new file mode 100644 index 0000000..fe88c9e --- /dev/null +++ b/docs/FLAVOUR_SUPPORT.md @@ -0,0 +1,20 @@ +# Flavour support + +| Flavour | Syntax | Execution | Replacement | Captures | Trace | Native offsets | +| ------------ | ------------------------------------------------------------------ | ----------------------------------------------- | ---------------------------------------------------- | -------------------------------------------------- | ----------- | ------------------- | +| ECMAScript | regexpp 4.12.2 grammar; explanations partial/feature-matrix tested | Current browser `RegExp`; feature-matrix tested | Native matches; bounded ECMAScript `GetSubstitution` | Named/numbered; final repeated capture; no history | Unavailable | UTF-16 | +| PCRE2 | Unavailable in release | Technical spike only; not shipped | Unavailable | Unavailable | Unavailable | Planned UTF-8 bytes | +| PCRE | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | +| Python `re` | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Planned code points | +| Go `regexp` | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Planned UTF-8 bytes | +| Rust `regex` | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Planned UTF-8 bytes | +| .NET | Unavailable | Unavailable | Unavailable | Planned capture history | Unavailable | Planned UTF-16 | +| Java | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Planned UTF-16 | + +ECMAScript tests cover `g`, `y`, `u`, internal `d`, zero-length iteration, +named groups, unmatched/empty/repeated groups, lookbehind, backreferences, +Unicode properties, astral input, replacement and result truncation. + +Actual browser identity is displayed because ECMAScript behavior can evolve +with the runtime. Syntax acceptance and engine compilation are separate; +neither silently rewrites the pattern. diff --git a/docs/PARSER_PROFILES.md b/docs/PARSER_PROFILES.md new file mode 100644 index 0000000..b92f89c --- /dev/null +++ b/docs/PARSER_PROFILES.md @@ -0,0 +1,16 @@ +# Syntax provider profile + +Regex Tools has one public profile: + +```text +community +``` + +It uses only reviewed open-source dependencies and builds without private +registry credentials. Version 0.1.0 parses ECMAScript with regexpp 4.12.2. + +The product deliberately does not implement or reference an `@r101/parser` +profile. No commercial package alias, import, tarball, credential, licence key +or private CI path exists. Additional flavour syntax will be implemented +incrementally as open-source providers behind the same application-owned +interface. diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md new file mode 100644 index 0000000..cbf1776 --- /dev/null +++ b/docs/PERFORMANCE.md @@ -0,0 +1,66 @@ +# Performance + +Live parsing is debounced by 120 ms and live execution by 220 ms. Live runs use +a 250 ms wall-clock limit; manual runs default to 2 seconds and can be raised to +the configured 10-second maximum. + +Tests exercise bounded parsing, execution, zero-length iteration, result +truncation and worker recovery. Browser performance depends on hardware, +browser, engine version, pattern and subject. The UI therefore reports observed +elapsed time and exact runtime identity but makes no universal throughput or +safety claim. + +The production bundle separates syntax and execution workers. Interactive +syntax/extraction trees and editor decoration sets render at most 2,000 items +each and report both the rendered and actual totals. These are presentation +limits only: normalized syntax, exact ranges and bounded engine results remain +available to the capture table and eligible exports. + +Replacement templates have a 64 Ki UTF-16 input limit. Every token below that +limit remains in the syntax model; the editor decorates at most 2,000 tokens, +the token list renders 500 rows, and at most 200 replacement diagnostics +(including an omission summary) cross into the UI. List templates have a 16 Ki +UTF-16 input limit and render 250 token chips. + +The per-match replacement view renders at most 200 matches and 2,000 +contributions, with a combined 50,000 token-evaluation budget. Original, +replacement and individual contribution text are previewed separately from the +bounded output. + +Capture rows are collected under the central 100,000-row limit and rendered in +pages of 200. Individual result-value previews are capped at 64 Ki UTF-16 units +and share an 8 Mi-unit aggregate engine-result budget. The extraction tree +shows 100 UTF-16 units per value and capture-table cells show 512, with explicit +range lengths and separate engine-prefix labels; copy and eligible JSON export +retain the bounded engine value. List export is disabled when a source value is incomplete, so a bounded +preview cannot be mistaken for the full value. List generation additionally +stops at 250,000 template-token evaluations, a 64 Ki UTF-16 row preview, or an +8 Mi-unit aggregate materialization bound. The UI shows 500 list rows while an +eligible export still contains every generated row; any incomplete result +disables export. +Replacement output has a separate 64 MiB bound. Match/capture-limit +incompleteness and output-byte truncation are tracked separately; either makes +the aggregate replacement result incomplete. + +Unit-test cases run through supervisors separate from the interactive workers. +Each case uses its configured timeout, cancellation kills the active worker, +and the suite has a 60-second aggregate wall limit. Suite state is capped at +1,000 cases; add, clone, current-result capture and JSON append-import all +enforce that cap and preflight a 32 MiB aggregate in-memory suite bound. Test +names, patterns, subjects, replacement templates and expected values have +explicit per-field limits; subjects must also fit the execution engine's UTF-8 +byte cap. Failure diagnostics preview only bounded portions of expected and +actual values and retain at most 2 KiB UTF-8 per result, keeping 1,000 result +messages below the 2 MiB log budget. A complete replacement output above the +expected-value or aggregate-suite limit is not copied into draft state; a +lightweight `should-match` draft is offered when applicable. + +Project import, export and local save have a 32 MiB aggregate UTF-8 JSON limit +in addition to field-specific limits. Standalone test-suite import has the same +32 MiB pre-parse file/document limit. Export and save count the indented JSON +representation against a byte budget before materializing or writing it. +IndexedDB schema version 2 indexes `updatedAt`; latest-project loading opens one +descending cursor and validates only that record instead of calling `getAll()`. + +Formal cold/warm benchmarks, p95 statistics and growth charts are deferred to +the analysis milestone and will run in killable workers. diff --git a/docs/PORTAL_REQUIREMENTS.md b/docs/PORTAL_REQUIREMENTS.md new file mode 100644 index 0000000..c6f2530 --- /dev/null +++ b/docs/PORTAL_REQUIREMENTS.md @@ -0,0 +1,19 @@ +# Portal requirements + +Regex Tools 0.1.0 is a static nested-path-safe application. + +Required delivery behavior: + +- JavaScript workers use a JavaScript MIME type. +- `toolbox-app.json`, HTML and legal/source documents should remain + revalidation/no-cache resources. +- Hashed Vite assets may use immutable long-term caching. +- CSP must allow `worker-src 'self' blob:`. +- No WebAssembly MIME rule or `wasm-unsafe-eval` is required by v0.1.0. + +The Portal must consume the immutable release ZIP, verify its SHA-256, verify +manifest ID `de.add-ideas.regex-tools` and version `0.1.0`, and mount it at a +relative target such as `apps/regex/`. + +When PCRE2 ships later, the smallest additional change will be +`application/wasm` delivery and `script-src 'self' 'wasm-unsafe-eval'`. diff --git a/docs/REFERENCE_IMPLEMENTATIONS.md b/docs/REFERENCE_IMPLEMENTATIONS.md new file mode 100644 index 0000000..8883c03 --- /dev/null +++ b/docs/REFERENCE_IMPLEMENTATIONS.md @@ -0,0 +1,21 @@ +# Reference implementations and sources + +Inspection date: 2026-07-24. + +| Reference | Exact revision/version | Licence | Use and copying status | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------- | +| Regex Tools | Unborn repository at initial inspection; release identity `v0.1.0` | GPL-3.0-or-later | Application source written for this project. | +| Toolbox SDK | `53c40a61ba1581246f65773fcbb1c1cfd31ac98e` / 0.2.2 | Apache-2.0 | Contract, shell and release conventions; package APIs used, no source adapted. | +| Toolbox Portal | `a9c31c8986c40a0097966318e925083302e91e13` / 0.5.0 | AGPL-3.0-only | Assembly/lock conventions inspected; no source copied into the app. | +| regexpp | 4.12.2; npm integrity `sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==` | MIT | Pinned ECMAScript syntax provider. Provider AST is normalized in a worker. | +| PCRE2 | signed tag 10.47; commit `f454e231fe5006dd7ff8f4693fd2b8eb94333429` | BSD-3-Clause WITH PCRE2-exception | External technical spike only; not shipped or copied. | +| ECMAScript specification | ; living specification inspected 2026-07-24 | ECMA terms | Semantics reference; no prose copied. | +| regex101 | and public parser documentation | Product/commercial terms | Product reference only. No source, branding, explanations, reference prose or visual design copied. | +| `recheck` | 4.5.0 | MIT | Metadata inspected; not installed or shipped. | +| `regexp-ast-analysis` | 0.7.1 | MIT | Metadata inspected; not installed or shipped. | +| RegexHub | repository revision `d1d9f19d259745dfdd21935b9ef3e747b62b9bfb` | MIT | Potential future adversarial cases; no file copied. | +| RegexLib | Website inspected | Redistribution licence not established | No fixture copied. | +| RGXP.RU | Website inspected | Redistribution licence not established | No fixture copied. | + +All v0.1.0 conformance cases are project-authored. See +`tests/fixtures/README.md`. diff --git a/docs/RELEASE.md b/docs/RELEASE.md new file mode 100644 index 0000000..7fa1c86 --- /dev/null +++ b/docs/RELEASE.md @@ -0,0 +1,23 @@ +# Release process + +1. Ensure the intended commit is clean and tagged. +2. Install the exact lockfile with `npm ci`. +3. Run `npm run release:artifact`. +4. Record the reported SHA-256, then run + `npm run package:release -- --force` twice and verify both reported digests + match it. Inspect an existing exact-version artifact before deliberately + replacing it. +5. Inspect ZIP paths, manifest, legal documents and absence of maps/secrets. +6. Publish ZIP and `.sha256` on the matching Gitea release. +7. Use the public immutable URL and digest in Toolbox Portal. + +The package script sorts paths without locale-dependent collation, rejects +symlinks and credential-like paths, normalizes file timestamps and modes, +verifies manifest/source identity, stages both outputs and rolls back the pair +on publication failure, replaces an existing exact version only with +`--force`, and produces: + +```text +release/regex-tools-0.1.0.zip +release/regex-tools-0.1.0.zip.sha256 +``` diff --git a/docs/SECURITY.md b/docs/SECURITY.md new file mode 100644 index 0000000..9b2efd0 --- /dev/null +++ b/docs/SECURITY.md @@ -0,0 +1,51 @@ +# Security and privacy + +Patterns, flags, replacement templates, subjects and imported projects are +untrusted. + +- Syntax and execution use separate workers. +- Regex execution never occurs on the UI thread. +- Timeout, crash and cancellation terminate the worker. +- A terminated worker is never reused. +- Pattern, subject, replacement/list template, match, capture and output sizes + are bounded. +- Replacement templates are strings; executable callbacks are unavailable. +- Replacement/list token decorations, rows, chips, diagnostics and list + evaluation work have separate presentation/operation caps; incomplete list + results cannot be exported. +- Per-match replacement mapping executes no user code and is capped at 200 + matches, 2,000 materialized contributions and 50,000 token evaluations. +- Imported JSON is aggregate-size-bounded before parsing, then field-validated + and never evaluated or auto-run. Export and IndexedDB save apply the same + 32 MiB aggregate UTF-8 document bound before materialization or persistence. +- Project and standalone-suite test subjects are not exported by default, and + unit-test subjects require a separate opt-in before IndexedDB persistence. +- Unit-test add, update, clone and append-import paths share 1,000-case and + 32 MiB aggregate state caps. +- Unit-test assertion diagnostics never stringify complete large values and + retain at most 2 KiB UTF-8 per case. Oversized replacement output is not + copied into an exact current-result draft. +- No backend, uploads, telemetry, remote corpus URL, CDN, remote font, `eval`, + `Function`, arbitrary command line or unsafe HTML rendering exists. + +Version 0.1.0 needs a CSP equivalent to: + +```text +default-src 'self'; +script-src 'self'; +worker-src 'self' blob:; +connect-src 'self'; +img-src 'self' data:; +font-src 'self'; +style-src 'self' 'unsafe-inline'; +object-src 'none'; +frame-src 'none'; +base-uri 'none'; +form-action 'none'; +``` + +The Toolbox shell currently requires inline styles. No `unsafe-eval` or +`wasm-unsafe-eval` is required until an actual WebAssembly flavour ships. + +Report vulnerabilities privately to the repository owner before public issue +details are posted. diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..f930094 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,43 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { + ignores: [ + "dist", + "release", + "coverage", + "test-results", + "playwright-report", + ], + }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ["**/*.{ts,tsx}"], + languageOptions: { + ecmaVersion: 2023, + globals: { + ...globals.browser, + ...globals.worker, + }, + }, + plugins: { + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "react-refresh/only-export-components": [ + "warn", + { allowConstantExport: true }, + ], + }, + }, + { + files: ["scripts/**/*.mjs", "playwright.config.ts"], + languageOptions: { globals: globals.node }, + }, +); diff --git a/index.html b/index.html new file mode 100644 index 0000000..9bdbfbc --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + + + + Regex Tools + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..cf94e2b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4166 @@ +{ + "name": "regex-tools", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "regex-tools", + "version": "0.1.0", + "license": "GPL-3.0-or-later", + "dependencies": { + "@add-ideas/toolbox-contract": "0.2.2", + "@add-ideas/toolbox-shell-react": "0.2.2", + "@codemirror/commands": "6.10.4", + "@codemirror/language": "6.12.4", + "@codemirror/search": "6.7.1", + "@codemirror/state": "6.7.1", + "@codemirror/view": "6.43.6", + "@eslint-community/regexpp": "4.12.2", + "@lezer/highlight": "1.2.3", + "react": "19.2.7", + "react-dom": "19.2.7" + }, + "devDependencies": { + "@add-ideas/toolbox-testkit": "0.2.2", + "@eslint/js": "10.0.1", + "@playwright/test": "1.61.1", + "@testing-library/jest-dom": "6.9.1", + "@testing-library/react": "16.3.2", + "@testing-library/user-event": "14.6.1", + "@types/node": "25.9.5", + "@types/react": "19.2.17", + "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "6.0.3", + "eslint": "10.7.0", + "eslint-plugin-react-hooks": "7.1.1", + "eslint-plugin-react-refresh": "0.5.2", + "fflate": "0.8.3", + "globals": "17.7.0", + "jsdom": "29.1.1", + "prettier": "3.9.5", + "typescript": "6.0.3", + "typescript-eslint": "8.64.0", + "vite": "8.1.5", + "vitest": "4.1.10" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@add-ideas/toolbox-contract": { + "version": "0.2.2", + "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-contract/-/0.2.2/toolbox-contract-0.2.2.tgz", + "integrity": "sha512-VcZ8j4O2PgFaIYgxs6r9u0uPxA+hHKwhCW+omfeVCtbjAAYhH2KhRlBGm0ERVVYcK5kchyzZLXrC4LKWRotVzg==", + "license": "Apache-2.0" + }, + "node_modules/@add-ideas/toolbox-shell-react": { + "version": "0.2.2", + "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-shell-react/-/0.2.2/toolbox-shell-react-0.2.2.tgz", + "integrity": "sha512-w/xbLCd50a2Jq7vQ9Z9ygUOuqlOCRkIYlk4vSJx0mf4REJNeMYi3PE2MbaLUC4DkQWyrmatsa5HNT89o0l91BA==", + "license": "Apache-2.0", + "dependencies": { + "@add-ideas/toolbox-contract": "0.2.2" + }, + "peerDependencies": { + "react": ">=18 <20", + "react-dom": ">=18 <20" + } + }, + "node_modules/@add-ideas/toolbox-testkit": { + "version": "0.2.2", + "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-testkit/-/0.2.2/toolbox-testkit-0.2.2.tgz", + "integrity": "sha512-ZxrRYiJI/j5aXwcd3mQItgDl1iOJ6N26+MpIG18qVZNwKKFCTCfSTzgzRooPt8rRc1MQYTPo4c+l/QpOWuTXxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@add-ideas/toolbox-contract": "0.2.2" + }, + "bin": { + "toolbox-check": "dist/cli.js" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "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_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "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_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "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/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.4.tgz", + "integrity": "sha512-Ryk9y9T0FFVF0cUGhAknveAyUOl/A1qReTFi+qPKtOh2Z9F4AUBz3XOrYD4ZEgZirdugVzHvd/2/Wcwy5OliTg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.7.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", + "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/search": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.1.tgz", + "integrity": "sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.1.tgz", + "integrity": "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.43.6", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.6.tgz", + "integrity": "sha512-EVunGSYN1wz1p75WY1s3Xg7t3i8Yol0kGZGizNdX9BUFgMFILYVe8/u6EVpo7Ff5PwbZuILb4QAq7IZoKzIEQA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz", + "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", + "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@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/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@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/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.3.tgz", + "integrity": "sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==", + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@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": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@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", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@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, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@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/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@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": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.5.tgz", + "integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", + "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/type-utils": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.64.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", + "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", + "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.64.0", + "@typescript-eslint/types": "^8.64.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", + "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", + "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", + "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", + "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", + "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.64.0", + "@typescript-eslint/tsconfig-utils": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", + "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", + "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", + "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.1.tgz", + "integrity": "sha512-HYXq73DDpCtNzOmrFsm9eSwCvWCql0RzqjpDzXN9EadiLJ4DNat0nsZ/Bzmy+Ud12mb4/zKDY0cQ805ZzN+i0A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "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": { + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.396", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.396.tgz", + "integrity": "sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz", + "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.3.tgz", + "integrity": "sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/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/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "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.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "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.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "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.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "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.npmjs.org/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.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "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/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/postcss": { + "version": "8.5.22", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz", + "integrity": "sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", + "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@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/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.9", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.9.tgz", + "integrity": "sha512-3kZ8wQQ/k5DrChD4X4FVvr2D7E5uoRgAqkPyLpSCGUvqOvqu+JEdr3mwMUaVWb+vMHZaKhF5fp2PBigKsui7hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.9" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.9", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.9.tgz", + "integrity": "sha512-DxKfPBI52p2msTEu7MPhdpdDTBhhVQg1a/8PjQckeyAvO13eMYElX545grIp6nnTGIMZlRvFZPvFhvI/WIz2Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz", + "integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.64.0", + "@typescript-eslint/parser": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/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": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@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/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9105b5e --- /dev/null +++ b/package.json @@ -0,0 +1,81 @@ +{ + "name": "regex-tools", + "version": "0.1.0", + "description": "Develop, explain, test and apply regular expressions locally in the browser.", + "license": "GPL-3.0-or-later", + "author": "Albrecht Degering", + "repository": { + "type": "git", + "url": "git+https://git.add-ideas.de/zemion/regex-tools.git" + }, + "homepage": "https://git.add-ideas.de/zemion/regex-tools", + "bugs": { + "url": "https://git.add-ideas.de/zemion/regex-tools/issues" + }, + "private": true, + "type": "module", + "engines": { + "node": ">=22" + }, + "scripts": { + "predev": "npm run manifest:generate", + "dev": "vite", + "prebuild": "npm run manifest:generate", + "build": "tsc -b && vite build", + "preview": "vite preview", + "typecheck": "tsc -b --pretty false", + "lint": "eslint . --max-warnings=0", + "format": "prettier --write .", + "format:check": "prettier --check .", + "test": "vitest run", + "test:watch": "vitest", + "test:conformance": "vitest run tests/conformance", + "test:browser": "playwright test", + "engines:build": "node scripts/build-engines.mjs", + "engines:verify": "node scripts/verify-engine-assets.mjs", + "manifest:generate": "node scripts/generate-toolbox-manifest.mjs", + "manifest:check": "node scripts/generate-toolbox-manifest.mjs --check", + "toolbox:check": "toolbox-check dist", + "package:release": "node scripts/package-release.mjs", + "checksum:release": "node scripts/checksum-release.mjs", + "check": "npm run manifest:check && npm run typecheck && npm run lint && npm run format:check && npm run test && npm run build && npm run engines:verify && npm run toolbox:check", + "release:artifact": "npm run check && npm run test:browser && npm run package:release" + }, + "dependencies": { + "@add-ideas/toolbox-contract": "0.2.2", + "@add-ideas/toolbox-shell-react": "0.2.2", + "@codemirror/commands": "6.10.4", + "@codemirror/language": "6.12.4", + "@codemirror/search": "6.7.1", + "@codemirror/state": "6.7.1", + "@codemirror/view": "6.43.6", + "@eslint-community/regexpp": "4.12.2", + "@lezer/highlight": "1.2.3", + "react": "19.2.7", + "react-dom": "19.2.7" + }, + "devDependencies": { + "@add-ideas/toolbox-testkit": "0.2.2", + "@eslint/js": "10.0.1", + "@playwright/test": "1.61.1", + "@testing-library/jest-dom": "6.9.1", + "@testing-library/react": "16.3.2", + "@testing-library/user-event": "14.6.1", + "@types/node": "25.9.5", + "@types/react": "19.2.17", + "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "6.0.3", + "eslint": "10.7.0", + "eslint-plugin-react-hooks": "7.1.1", + "eslint-plugin-react-refresh": "0.5.2", + "fflate": "0.8.3", + "globals": "17.7.0", + "jsdom": "29.1.1", + "prettier": "3.9.5", + "typescript": "6.0.3", + "typescript-eslint": "8.64.0", + "vite": "8.1.5", + "vitest": "4.1.10" + }, + "packageManager": "npm@11.17.0" +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..8d5c452 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,25 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./tests/browser", + fullyParallel: false, + timeout: 120_000, + expect: { timeout: 10_000 }, + reporter: [["list"]], + use: { + baseURL: "http://127.0.0.1:4173", + trace: "retain-on-failure", + }, + webServer: { + command: "npm run build && node scripts/serve-test.mjs", + url: "http://127.0.0.1:4173", + reuseExistingServer: !process.env.CI, + timeout: 180_000, + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], +}); diff --git a/public/engines/README.md b/public/engines/README.md new file mode 100644 index 0000000..a8ecff3 --- /dev/null +++ b/public/engines/README.md @@ -0,0 +1,7 @@ +# Engine assets + +Regex Tools 0.1.0 executes ECMAScript through the browser's native `RegExp` +engine. It therefore ships no external engine binary or WebAssembly pack. + +Future flavour packs will be built from pinned, documented open-source +revisions and placed in versioned subdirectories here. diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..d16e598 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,7 @@ + + Regex Tools + + + + + diff --git a/public/toolbox-app.json b/public/toolbox-app.json new file mode 100644 index 0000000..2322940 --- /dev/null +++ b/public/toolbox-app.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://git.add-ideas.de/zemion/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json", + "schemaVersion": 1, + "id": "de.add-ideas.regex-tools", + "name": "Regex Tools", + "version": "0.1.0", + "description": "Develop, explain, test and apply regular expressions locally in the browser.", + "entry": "./", + "icon": "./favicon.svg", + "categories": ["developer", "text", "regex"], + "tags": ["regular-expression", "match", "replace", "explain", "test"], + "integration": { + "contextVersion": 1, + "launchModes": ["navigate", "new-tab"], + "embedding": "unsupported" + }, + "requirements": { + "secureContext": false, + "workers": true, + "indexedDb": true, + "crossOriginIsolated": false, + "topLevelContext": false + }, + "privacy": { + "processing": "local", + "fileUploads": false, + "telemetry": false + }, + "source": { + "repository": "https://git.add-ideas.de/zemion/regex-tools", + "license": "GPL-3.0-or-later" + }, + "actions": [ + { + "id": "source", + "label": "Source", + "url": "https://git.add-ideas.de/zemion/regex-tools" + } + ] +} diff --git a/scripts/build-engines.mjs b/scripts/build-engines.mjs new file mode 100644 index 0000000..b254aaf --- /dev/null +++ b/scripts/build-engines.mjs @@ -0,0 +1,24 @@ +import { readFile, readdir } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const packageJson = JSON.parse( + await readFile(path.join(root, "package.json"), "utf8"), +); +const engineDirectory = path.join(root, "public", "engines"); +const entries = (await readdir(engineDirectory)).sort(); + +if ( + packageJson.version !== "0.1.0" || + entries.length !== 1 || + entries[0] !== "README.md" +) { + throw new Error( + "The ECMAScript release must contain only the documented native-browser engine placeholder.", + ); +} + +console.log( + "Regex Tools 0.1.0 uses the native browser RegExp engine; no external engine pack needs building.", +); diff --git a/scripts/checksum-release.mjs b/scripts/checksum-release.mjs new file mode 100644 index 0000000..5262d19 --- /dev/null +++ b/scripts/checksum-release.mjs @@ -0,0 +1,12 @@ +import { createHash } from "node:crypto"; + +export function sha256(data) { + return createHash("sha256").update(data).digest("hex"); +} + +export function checksumLine(digest, fileName) { + if (!/^[a-f0-9]{64}$/u.test(digest) || /[\/\r\n]/u.test(fileName)) { + throw new Error("Invalid checksum output"); + } + return `${digest} ${fileName}\n`; +} diff --git a/scripts/generate-toolbox-manifest.mjs b/scripts/generate-toolbox-manifest.mjs new file mode 100644 index 0000000..d677ac0 --- /dev/null +++ b/scripts/generate-toolbox-manifest.mjs @@ -0,0 +1,42 @@ +import { readFile, writeFile } from "node:fs/promises"; +import { dirname, join, relative } from "node:path"; +import { fileURLToPath } from "node:url"; +import { format } from "prettier"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); +const sourcePath = join(root, "src", "toolbox", "manifest.source.json"); +const outputPath = join(root, "public", "toolbox-app.json"); +const packagePath = join(root, "package.json"); +const checkOnly = process.argv.includes("--check"); + +const source = JSON.parse(await readFile(sourcePath, "utf8")); +const packageJson = JSON.parse(await readFile(packagePath, "utf8")); + +if (source.version !== packageJson.version) { + throw new Error( + `Manifest version ${source.version} differs from package version ${packageJson.version}`, + ); +} + +if ( + source.source?.repository !== "https://git.add-ideas.de/zemion/regex-tools" || + source.source?.license !== "GPL-3.0-or-later" +) { + throw new Error("Manifest source identity is incomplete or inconsistent"); +} + +const serialized = await format(JSON.stringify(source), { + filepath: outputPath, +}); +if (checkOnly) { + const current = await readFile(outputPath, "utf8").catch(() => ""); + if (current !== serialized) { + throw new Error( + `${relative(root, outputPath)} is stale; run npm run manifest:generate`, + ); + } + console.log("Toolbox manifest is synchronized"); +} else { + await writeFile(outputPath, serialized); + console.log(`Generated ${relative(root, outputPath)}`); +} diff --git a/scripts/package-release.mjs b/scripts/package-release.mjs new file mode 100644 index 0000000..e3d62aa --- /dev/null +++ b/scripts/package-release.mjs @@ -0,0 +1,526 @@ +import { + lstat, + mkdir, + mkdtemp, + readFile, + readdir, + rename, + rm, + writeFile, +} from "node:fs/promises"; +import path from "node:path"; +import { createRequire } from "node:module"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { zipSync } from "fflate"; +import { checksumLine, sha256 } from "./checksum-release.mjs"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const zipEpoch = new Date(1980, 0, 1, 0, 0, 0); +const gplVersion3TextSha256 = + "fb981668c18a279e285fc4d83fba1e836cc84dd4daa73c9697d3cfd2d8aca6e0"; +const requiredRootFiles = [ + "LICENSE", + "README.md", + "CHANGELOG.md", + "SOURCE.md", + "THIRD_PARTY_NOTICES.md", +]; + +function compareText(left, right) { + return left < right ? -1 : left > right ? 1 : 0; +} + +export function safeArchivePath(fileName) { + if ( + typeof fileName !== "string" || + fileName.length === 0 || + fileName.includes("\0") || + fileName.includes("\\") || + fileName.startsWith("/") || + /^[A-Za-z]:\//u.test(fileName) + ) { + throw new Error(`Invalid release path: ${String(fileName)}`); + } + const parts = fileName.split("/"); + if ( + parts.some( + (component) => + component === "" || component === "." || component === "..", + ) || + path.posix.normalize(fileName) !== fileName + ) { + throw new Error(`Non-canonical release path: ${fileName}`); + } + return fileName; +} + +async function collectDirectory(directory, prefix = "") { + const result = []; + const visit = async (current) => { + const children = await readdir(current, { withFileTypes: true }); + children.sort((left, right) => compareText(left.name, right.name)); + for (const child of children) { + const absolute = path.join(current, child.name); + const details = await lstat(absolute); + if (details.isSymbolicLink()) { + throw new Error(`Release input contains a symlink: ${absolute}`); + } + if (details.isDirectory()) { + await visit(absolute); + } else if (details.isFile()) { + const relative = path + .relative(directory, absolute) + .split(path.sep) + .join("/"); + result.push({ + name: safeArchivePath( + prefix ? path.posix.join(prefix, relative) : relative, + ), + data: await readFile(absolute), + }); + } else { + throw new Error(`Release input contains a special file: ${absolute}`); + } + } + }; + await visit(directory); + return result; +} + +function safePackageDirectory(name, version) { + const normalized = `${name.replace(/^@/u, "").replaceAll("/", "__")}-${ + version + }`; + if (!/^[A-Za-z0-9._@+-]+$/u.test(normalized)) { + throw new Error(`Unsafe package identity: ${name}@${version}`); + } + return normalized; +} + +async function collectRuntimePackages(packageJson) { + const packages = new Map(); + const visit = async (name, fromDirectory) => { + const resolver = createRequire(path.join(fromDirectory, "package.json")); + let packagePath; + try { + packagePath = resolver.resolve(`${name}/package.json`); + } catch { + const entry = resolver.resolve(name); + let directory = path.dirname(entry); + while (directory !== path.dirname(directory)) { + const candidate = path.join(directory, "package.json"); + const details = await lstat(candidate).catch(() => null); + if (details?.isFile()) { + const candidateJson = JSON.parse(await readFile(candidate, "utf8")); + if (candidateJson.name === name) { + packagePath = candidate; + break; + } + } + directory = path.dirname(directory); + } + } + if (!packagePath) { + throw new Error(`Could not resolve runtime package ${name}.`); + } + const details = JSON.parse(await readFile(packagePath, "utf8")); + const key = `${details.name}@${details.version}`; + if (packages.has(key)) return; + const directory = path.dirname(packagePath); + packages.set(key, { directory, details }); + for (const dependency of Object.keys(details.dependencies ?? {}).sort()) { + await visit(dependency, directory); + } + }; + for (const dependency of Object.keys(packageJson.dependencies ?? {}).sort()) { + await visit(dependency, root); + } + return [...packages.values()].sort((left, right) => + compareText( + `${left.details.name}@${left.details.version}`, + `${right.details.name}@${right.details.version}`, + ), + ); +} + +async function collectRuntimeLicenceEntries(packageJson) { + const packages = await collectRuntimePackages(packageJson); + const entries = []; + const rows = [ + "# Runtime npm dependency licences", + "", + "| Package | Version | Licence | Repository |", + "| --- | --- | --- | --- |", + ]; + for (const runtimePackage of packages) { + const { details, directory } = runtimePackage; + const legalFiles = (await readdir(directory, { withFileTypes: true })) + .filter( + (entry) => + entry.isFile() && + /^(?:licen[cs]e|copying|notice)(?:\.|$)/iu.test(entry.name), + ) + .sort((left, right) => compareText(left.name, right.name)); + if (legalFiles.length === 0) { + throw new Error( + `Runtime package ${details.name}@${details.version} has no licence file.`, + ); + } + const packageDirectory = safePackageDirectory( + details.name, + details.version, + ); + for (const legalFile of legalFiles) { + entries.push({ + name: safeArchivePath( + `LICENSES/npm/${packageDirectory}/${legalFile.name}`, + ), + data: await readFile(path.join(directory, legalFile.name)), + }); + } + const repository = + typeof details.repository === "string" + ? details.repository + : (details.repository?.url ?? ""); + rows.push( + `| \`${details.name}\` | ${details.version} | ${ + details.license ?? "See packaged licence" + } | ${String(repository).replaceAll("|", "\\|")} |`, + ); + } + entries.push({ + name: "LICENSES/npm/DEPENDENCIES.md", + data: Buffer.from(`${rows.join("\n")}\n`), + }); + return entries; +} + +async function collectBuildGeneratedLicenceEntries() { + const resolver = createRequire(path.join(root, "package.json")); + const dependencies = [ + { + name: "rolldown", + version: "1.1.5", + license: "MIT", + legalFile: "LICENSE", + }, + { + name: "vite", + version: "8.1.5", + license: "MIT", + legalFile: "LICENSE.md", + }, + ]; + const entries = []; + for (const dependency of dependencies) { + const packagePath = resolver.resolve(`${dependency.name}/package.json`); + const details = JSON.parse(await readFile(packagePath, "utf8")); + if ( + details.name !== dependency.name || + details.version !== dependency.version || + details.license !== dependency.license + ) { + throw new Error( + `Build dependency identity drifted: expected ${dependency.name}@${dependency.version} (${dependency.license}).`, + ); + } + const legalPath = path.join( + path.dirname(packagePath), + dependency.legalFile, + ); + const legalDetails = await lstat(legalPath); + if (legalDetails.isSymbolicLink() || !legalDetails.isFile()) { + throw new Error( + `Build dependency legal file is not a regular file: ${legalPath}.`, + ); + } + entries.push({ + name: safeArchivePath( + `LICENSES/build/${safePackageDirectory( + details.name, + details.version, + )}/${dependency.legalFile}`, + ), + data: await readFile(legalPath), + }); + } + return entries; +} + +function scanEntry(entry) { + if (entry.name.endsWith(".map")) { + throw new Error(`Source map is not approved for release: ${entry.name}`); + } + const components = entry.name.toLowerCase().split("/"); + if ( + components.some((component) => + [ + ".env", + ".npmrc", + ".git", + "credentials", + "id_rsa", + "id_ed25519", + ].includes(component), + ) || + /\.(?:key|pem|p12|pfx)$/iu.test(entry.name) + ) { + throw new Error(`Credential-like release path: ${entry.name}`); + } + if (!/\.(?:css|html|js|json|md|svg|txt)$/iu.test(entry.name)) return; + const text = entry.data.toString("utf8"); + if (/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/u.test(text)) { + throw new Error(`Private key material detected in ${entry.name}`); + } + if (/https?:\/\/[^/@\s]+:[^/@\s]+@/iu.test(text)) { + throw new Error(`Credential-bearing URL detected in ${entry.name}`); + } + if ( + /(?:^|[\s"'`])(?:\/home\/|\/mnt\/|\/Users\/|[A-Za-z]:\\Users\\)/mu.test( + text, + ) + ) { + throw new Error(`Local absolute path detected in ${entry.name}`); + } +} + +export function createDeterministicZip(inputEntries) { + const entries = inputEntries + .map((entry) => ({ + name: safeArchivePath(entry.name), + data: Buffer.from(entry.data), + })) + .sort((left, right) => compareText(left.name, right.name)); + const seen = new Set(); + const zippable = Object.create(null); + let totalBytes = 0; + for (const entry of entries) { + if (seen.has(entry.name)) { + throw new Error(`Duplicate release path: ${entry.name}`); + } + seen.add(entry.name); + totalBytes += entry.data.byteLength; + if (entry.data.byteLength > 128 * 1024 * 1024) { + throw new Error(`Release entry is too large: ${entry.name}`); + } + if (totalBytes > 512 * 1024 * 1024) { + throw new Error("Release exceeds the 512 MiB uncompressed limit."); + } + scanEntry(entry); + zippable[entry.name] = [ + new Uint8Array(entry.data), + { + attrs: (0o100644 << 16) >>> 0, + level: 9, + mtime: zipEpoch, + os: 3, + }, + ]; + } + return Buffer.from( + zipSync(zippable, { + attrs: (0o100644 << 16) >>> 0, + level: 9, + mtime: zipEpoch, + os: 3, + }), + ); +} + +async function collectReleaseEntries() { + const packageJson = JSON.parse( + await readFile(path.join(root, "package.json"), "utf8"), + ); + if ( + packageJson.version !== "0.1.0" || + packageJson.license !== "GPL-3.0-or-later" || + packageJson.repository?.url !== + "git+https://git.add-ideas.de/zemion/regex-tools.git" + ) { + throw new Error("Package version, licence or source identity drifted."); + } + const licence = await readFile(path.join(root, "LICENSE"), "utf8"); + if ( + !licence.includes("GNU GENERAL PUBLIC LICENSE") || + !licence.includes("Version 3, 29 June 2007") || + !licence.includes("END OF TERMS AND CONDITIONS") || + sha256(Buffer.from(licence)) !== gplVersion3TextSha256 + ) { + throw new Error( + "Root LICENSE is not the exact complete GPL version 3 text.", + ); + } + const entries = await collectDirectory(path.join(root, "dist")); + entries.push( + ...(await collectDirectory(path.join(root, "LICENSES"), "LICENSES")), + ); + entries.push(...(await collectRuntimeLicenceEntries(packageJson))); + entries.push(...(await collectBuildGeneratedLicenceEntries())); + for (const fileName of requiredRootFiles) { + entries.push({ + name: fileName, + data: await readFile(path.join(root, fileName)), + }); + } + const names = new Set(entries.map((entry) => entry.name)); + for (const required of [ + "index.html", + "toolbox-app.json", + "favicon.svg", + "LICENSE", + "CHANGELOG.md", + "SOURCE.md", + "THIRD_PARTY_NOTICES.md", + "LICENSES/build/rolldown-1.1.5/LICENSE", + "LICENSES/build/vite-8.1.5/LICENSE.md", + ]) { + if (!names.has(required)) { + throw new Error(`Required release file is missing: ${required}`); + } + } + if (![...names].some((name) => name.startsWith("assets/"))) { + throw new Error("Release has no production assets."); + } + if (![...names].some((name) => name.startsWith("LICENSES/"))) { + throw new Error("Release has no third-party licence directory."); + } + const manifest = JSON.parse( + entries + .find((entry) => entry.name === "toolbox-app.json") + .data.toString("utf8"), + ); + if ( + manifest.id !== "de.add-ideas.regex-tools" || + manifest.version !== packageJson.version || + manifest.source?.repository !== + "https://git.add-ideas.de/zemion/regex-tools" || + manifest.source?.license !== "GPL-3.0-or-later" + ) { + throw new Error("Packaged manifest identity is inconsistent."); + } + const indexHtml = entries + .find((entry) => entry.name === "index.html") + .data.toString("utf8"); + if (/(?:src|href)\s*=\s*["']\/(?!\/)/iu.test(indexHtml)) { + throw new Error("index.html contains a root-absolute asset path."); + } + return { entries, packageJson }; +} + +export function parseReleaseArguments(argumentsList) { + const allowed = new Set(["--force"]); + const unknown = argumentsList.filter((argument) => !allowed.has(argument)); + if (unknown.length > 0) { + throw new Error(`Unknown release argument: ${unknown.join(", ")}`); + } + return { force: argumentsList.includes("--force") }; +} + +async function assertWritableTarget(target, force) { + try { + const details = await lstat(target); + if (details.isSymbolicLink() || !details.isFile()) { + throw new Error(`Release target is not a regular file: ${target}`); + } + if (!force) { + throw new Error( + `${path.relative(root, target)} exists; pass --force to replace this exact version.`, + ); + } + return true; + } catch (error) { + if (error && typeof error === "object" && error.code === "ENOENT") { + return false; + } + throw error; + } +} + +async function publishReleasePair({ + archive, + archivePath, + checksum, + checksumPath, + archiveExists, + checksumExists, +}) { + const releaseDirectory = path.dirname(archivePath); + const stage = await mkdtemp( + path.join(releaseDirectory, ".regex-tools-release-"), + ); + const stagedArchive = path.join(stage, path.basename(archivePath)); + const stagedChecksum = path.join(stage, path.basename(checksumPath)); + const previousArchive = path.join(stage, "previous-archive"); + const previousChecksum = path.join(stage, "previous-checksum"); + let archiveBackedUp = false; + let checksumBackedUp = false; + let archivePublished = false; + let checksumPublished = false; + try { + await writeFile(stagedArchive, archive, { flag: "wx", mode: 0o644 }); + await writeFile(stagedChecksum, checksum, { + encoding: "utf8", + flag: "wx", + mode: 0o644, + }); + if (archiveExists) { + await rename(archivePath, previousArchive); + archiveBackedUp = true; + } + if (checksumExists) { + await rename(checksumPath, previousChecksum); + checksumBackedUp = true; + } + await rename(stagedArchive, archivePath); + archivePublished = true; + await rename(stagedChecksum, checksumPath); + checksumPublished = true; + } catch (error) { + if (checksumPublished) await rm(checksumPath, { force: true }); + if (archivePublished) await rm(archivePath, { force: true }); + if (checksumBackedUp) await rename(previousChecksum, checksumPath); + if (archiveBackedUp) await rename(previousArchive, archivePath); + throw error; + } finally { + await rm(stage, { recursive: true, force: true }); + } +} + +async function main() { + const options = parseReleaseArguments(process.argv.slice(2)); + const { entries, packageJson } = await collectReleaseEntries(); + const archive = createDeterministicZip(entries); + const fileName = `regex-tools-${packageJson.version}.zip`; + const digest = sha256(archive); + const releaseDirectory = path.join(root, "release"); + await mkdir(releaseDirectory, { recursive: true }); + const releaseDirectoryDetails = await lstat(releaseDirectory); + if ( + releaseDirectoryDetails.isSymbolicLink() || + !releaseDirectoryDetails.isDirectory() + ) { + throw new Error("release/ must be a real directory."); + } + const archivePath = path.join(releaseDirectory, fileName); + const checksumPath = `${archivePath}.sha256`; + const archiveExists = await assertWritableTarget(archivePath, options.force); + const checksumExists = await assertWritableTarget( + checksumPath, + options.force, + ); + await publishReleasePair({ + archive, + archivePath, + checksum: checksumLine(digest, fileName), + checksumPath, + archiveExists, + checksumExists, + }); + console.log( + `Created release/${fileName} (${archive.byteLength} bytes, SHA-256 ${digest})`, + ); +} + +if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { + await main(); +} diff --git a/scripts/package-release.test.ts b/scripts/package-release.test.ts new file mode 100644 index 0000000..217b29b --- /dev/null +++ b/scripts/package-release.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from "vitest"; +import { + createDeterministicZip, + parseReleaseArguments, + safeArchivePath, +} from "./package-release.mjs"; +import { sha256 } from "./checksum-release.mjs"; + +describe("release packaging", () => { + it("creates a deterministic archive independent of input order", () => { + const entries = [ + { name: "index.html", data: Buffer.from("

ok

") }, + { name: "assets/app.js", data: Buffer.from("export {};") }, + ]; + expect(sha256(createDeterministicZip(entries))).toBe( + sha256(createDeterministicZip([...entries].reverse())), + ); + }); + + it.each(["../secret", "/absolute", "C:/absolute", "a\\b", "a/./b", ""])( + "rejects unsafe path %j", + (candidate) => { + expect(() => safeArchivePath(candidate)).toThrow(); + }, + ); + + it("rejects duplicate paths, source maps and credential-like entries", () => { + expect(() => + createDeterministicZip([ + { name: "index.html", data: Buffer.from("one") }, + { name: "index.html", data: Buffer.from("two") }, + ]), + ).toThrow("Duplicate release path"); + expect(() => + createDeterministicZip([ + { name: "assets/app.js.map", data: Buffer.from("{}") }, + ]), + ).toThrow("Source map"); + expect(() => + createDeterministicZip([{ name: ".env", data: Buffer.from("TOKEN=x") }]), + ).toThrow("Credential-like"); + }); + + it("requires an explicit force flag for exact-version replacement", () => { + expect(parseReleaseArguments([])).toEqual({ force: false }); + expect(parseReleaseArguments(["--force"])).toEqual({ force: true }); + expect(() => parseReleaseArguments(["--unknown"])).toThrow( + "Unknown release argument", + ); + }); +}); diff --git a/scripts/serve-test.mjs b/scripts/serve-test.mjs new file mode 100644 index 0000000..f888692 --- /dev/null +++ b/scripts/serve-test.mjs @@ -0,0 +1,79 @@ +import { createServer } from "node:http"; +import { readFile, stat } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + "..", + "dist", +); +const nestedPrefix = "/deep/nested/regex/"; +const mediaTypes = new Map([ + [".css", "text/css; charset=utf-8"], + [".html", "text/html; charset=utf-8"], + [".js", "text/javascript; charset=utf-8"], + [".json", "application/json; charset=utf-8"], + [".svg", "image/svg+xml"], + [".wasm", "application/wasm"], +]); +const testCatalogue = { + schemaVersion: 1, + id: "de.add-ideas.regex-tools.browser-test", + name: "Regex Tools browser-test Toolbox", + home: "./", + theme: { mode: "system", brand: "add·ideas" }, + apps: [{ manifest: "./toolbox-app.json", enabled: true }], +}; + +function safeFile(requestPath) { + const decoded = decodeURIComponent(requestPath); + const relative = decoded.startsWith(nestedPrefix) + ? decoded.slice(nestedPrefix.length) + : decoded.replace(/^\/+/u, ""); + const normalized = path.posix.normalize(relative || "index.html"); + if ( + normalized === ".." || + normalized.startsWith("../") || + path.isAbsolute(normalized) + ) { + return null; + } + return path.join(root, normalized); +} + +const server = createServer(async (request, response) => { + try { + const url = new URL(request.url ?? "/", "http://127.0.0.1"); + if (url.pathname === "/toolbox.catalog.json") { + response.writeHead(200, { + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "no-cache", + }); + response.end(JSON.stringify(testCatalogue)); + return; + } + let file = safeFile(url.pathname); + if (!file) { + response.writeHead(400).end("Bad request"); + return; + } + const details = await stat(file).catch(() => null); + if (details?.isDirectory()) file = path.join(file, "index.html"); + const content = await readFile(file); + response.writeHead(200, { + "Content-Type": + mediaTypes.get(path.extname(file)) ?? "application/octet-stream", + "Cache-Control": "no-cache", + "Cross-Origin-Resource-Policy": "same-origin", + }); + response.end(content); + } catch { + response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }); + response.end("Not found"); + } +}); + +server.listen(4173, "127.0.0.1", () => { + console.log("Test static server listening on http://127.0.0.1:4173"); +}); diff --git a/scripts/verify-engine-assets.mjs b/scripts/verify-engine-assets.mjs new file mode 100644 index 0000000..363ee04 --- /dev/null +++ b/scripts/verify-engine-assets.mjs @@ -0,0 +1,34 @@ +import { lstat, readFile, readdir } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const engineDirectory = path.join(root, "dist", "engines"); +const details = await lstat(engineDirectory); +if (details.isSymbolicLink() || !details.isDirectory()) { + throw new Error("dist/engines must be a real directory."); +} + +const entries = (await readdir(engineDirectory, { withFileTypes: true })).sort( + (left, right) => + left.name < right.name ? -1 : left.name > right.name ? 1 : 0, +); +if ( + entries.length !== 1 || + !entries[0]?.isFile() || + entries[0].name !== "README.md" +) { + throw new Error( + "Regex Tools 0.1.0 must not ship an undeclared external engine pack.", + ); +} + +const notice = await readFile(path.join(engineDirectory, "README.md"), "utf8"); +if ( + !notice.includes("native `RegExp`") || + !notice.includes("no external engine") +) { + throw new Error("The engine asset notice does not describe this release."); +} + +console.log("Verified ECMAScript-only engine assets (no WebAssembly pack)."); diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..51d4669 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,42 @@ +import { lazy, Suspense, useState } from "react"; +import { AppShell } from "@add-ideas/toolbox-shell-react"; +import "@add-ideas/toolbox-shell-react/styles.css"; +import "./styles.css"; +import { manifest } from "./toolbox/manifest"; +import { AppErrorBoundary } from "./app/AppErrorBoundary"; +import { HelpDialog } from "./components/HelpDialog"; + +const Workbench = lazy(async () => { + const module = await import("./components/Workbench"); + return { default: module.Workbench }; +}); + +export function App() { + const [helpOpen, setHelpOpen] = useState(false); + return ( + + setHelpOpen(true) }} + onContextError={(error) => { + console.warn( + "Toolbox context unavailable; continuing standalone.", + error, + ); + }} + > + + Preparing the local regex workbench… +

+ } + > + +
+
+ setHelpOpen(false)} /> +
+ ); +} diff --git a/src/app/AppErrorBoundary.tsx b/src/app/AppErrorBoundary.tsx new file mode 100644 index 0000000..ee2ebac --- /dev/null +++ b/src/app/AppErrorBoundary.tsx @@ -0,0 +1,37 @@ +import { Component, type ErrorInfo, type ReactNode } from "react"; + +export class AppErrorBoundary extends Component< + { readonly children: ReactNode }, + { readonly error: Error | null } +> { + state = { error: null as Error | null }; + + static getDerivedStateFromError(error: Error) { + return { error }; + } + + componentDidCatch(error: Error, info: ErrorInfo): void { + console.error("Regex Tools render failure", error, info); + } + + render() { + if (this.state.error) { + return ( +
+

Regex Tools could not render

+

{this.state.error.message}

+ +
+ ); + } + return this.props.children; + } +} diff --git a/src/browser/clipboard.test.ts b/src/browser/clipboard.test.ts new file mode 100644 index 0000000..d17f9b0 --- /dev/null +++ b/src/browser/clipboard.test.ts @@ -0,0 +1,52 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { writeClipboardText } from "./clipboard"; + +afterEach(() => { + vi.restoreAllMocks(); + Reflect.deleteProperty(document, "execCommand"); +}); + +describe("clipboard writer", () => { + it("uses the Clipboard API when available", async () => { + const writeText = vi.fn().mockResolvedValue(undefined); + Object.defineProperty(navigator, "clipboard", { + configurable: true, + value: { writeText }, + }); + + await writeClipboardText("complete value"); + + expect(writeText).toHaveBeenCalledWith("complete value"); + }); + + it("falls back after a secure-context or permission failure", async () => { + Object.defineProperty(navigator, "clipboard", { + configurable: true, + value: { writeText: vi.fn().mockRejectedValue(new Error("denied")) }, + }); + const execCommand = vi.fn().mockReturnValue(true); + Object.defineProperty(document, "execCommand", { + configurable: true, + value: execCommand, + }); + + await writeClipboardText("fallback value"); + + expect(execCommand).toHaveBeenCalledWith("copy"); + }); + + it("reports when neither copy mechanism is available", async () => { + Object.defineProperty(navigator, "clipboard", { + configurable: true, + value: undefined, + }); + Object.defineProperty(document, "execCommand", { + configurable: true, + value: vi.fn().mockReturnValue(false), + }); + + await expect(writeClipboardText("value")).rejects.toThrow( + /Clipboard access is unavailable/u, + ); + }); +}); diff --git a/src/browser/clipboard.ts b/src/browser/clipboard.ts new file mode 100644 index 0000000..f698595 --- /dev/null +++ b/src/browser/clipboard.ts @@ -0,0 +1,34 @@ +function legacyCopy(text: string): boolean { + if (typeof document.execCommand !== "function") return false; + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.readOnly = true; + textarea.setAttribute("aria-hidden", "true"); + textarea.style.position = "fixed"; + textarea.style.inset = "0 auto auto -10000px"; + textarea.style.opacity = "0"; + document.body.append(textarea); + textarea.select(); + textarea.setSelectionRange(0, textarea.value.length); + try { + return document.execCommand("copy"); + } finally { + textarea.remove(); + } +} + +export async function writeClipboardText(text: string): Promise { + try { + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(text); + return; + } + } catch { + // A permission or secure-context failure may still permit the local fallback. + } + if (!legacyCopy(text)) { + throw new Error( + "Clipboard access is unavailable in this browser context. Select and copy the value manually.", + ); + } +} diff --git a/src/components/CapabilityPanel.test.tsx b/src/components/CapabilityPanel.test.tsx new file mode 100644 index 0000000..431bf72 --- /dev/null +++ b/src/components/CapabilityPanel.test.tsx @@ -0,0 +1,88 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import type { RegexExecutionResult } from "../regex/model/match"; +import type { RegexSyntaxResult } from "../regex/model/syntax"; +import { CapabilityPanel } from "./CapabilityPanel"; + +const syntax: RegexSyntaxResult = { + accepted: true, + root: { + id: "root", + kind: "pattern", + range: { startUtf16: 0, endUtf16: 1 }, + raw: "a", + explanation: "Pattern", + children: [], + properties: { + zeroWidth: false, + nullable: false, + minimumLength: 1, + maximumLength: 1, + consumesInput: true, + }, + support: { flavour: "ecmascript", status: "supported", notes: [] }, + provenance: { + provider: "fixture", + providerVersion: "9", + source: "parsed", + }, + }, + tokens: [], + captures: [], + diagnostics: [], + provider: { id: "fixture-provider", version: "9" }, + coverage: { + status: "partial", + summary: "Fixture coverage summary", + unsupportedConstructs: ["fixture"], + }, +}; + +const execution: RegexExecutionResult = { + accepted: true, + engine: { + flavour: "ecmascript", + adapterVersion: "1", + engineName: "Fixture engine", + engineVersion: "123", + offsetUnit: "utf8-byte", + capabilities: { + compilation: true, + matching: true, + replacement: false, + namedCaptures: false, + captureHistory: false, + actualTrace: false, + benchmark: false, + }, + }, + flags: { + userFlags: "", + effectiveFlags: "d", + internallyAddedIndicesFlag: true, + internallyAddedGlobalFlag: false, + }, + matches: [], + diagnostics: [], + elapsedMs: 1, + truncated: false, +}; + +describe("CapabilityPanel", () => { + it("renders the current provider and engine metadata without static drift", () => { + render(); + + expect(screen.getByText("fixture-provider 9")).toBeInTheDocument(); + expect( + screen.getByText("partial: Fixture coverage summary"), + ).toBeInTheDocument(); + expect(screen.getByText("Fixture engine")).toBeInTheDocument(); + expect(screen.getByText("123")).toBeInTheDocument(); + expect(screen.getByText("UTF-8 bytes")).toBeInTheDocument(); + + const replacement = screen.getByText("Replacement").closest("div"); + expect(replacement).toHaveTextContent("Unavailable"); + const compilation = screen.getByText("Compilation").closest("div"); + expect(compilation).toHaveTextContent("Available"); + }); +}); diff --git a/src/components/CapabilityPanel.tsx b/src/components/CapabilityPanel.tsx new file mode 100644 index 0000000..2a4e8eb --- /dev/null +++ b/src/components/CapabilityPanel.tsx @@ -0,0 +1,122 @@ +import { REGEXPP_VERSION, SYNTAX_PROFILE } from "../version"; +import type { RegexExecutionResult } from "../regex/model/match"; +import type { RegexSyntaxResult } from "../regex/model/syntax"; +import type { RegexEngineCapabilities } from "../regex/model/flavour"; + +function capability( + capabilities: RegexEngineCapabilities | undefined, + key: keyof RegexEngineCapabilities, + unavailableDetail?: string, +): string { + if (!capabilities) return "Awaiting first engine result"; + if (capabilities[key]) return "Available"; + return unavailableDetail + ? `Unavailable — ${unavailableDetail}` + : "Unavailable"; +} + +function offsetLabel( + unit: RegexExecutionResult["engine"]["offsetUnit"] | undefined, +): string { + switch (unit) { + case "utf16": + return "UTF-16 code units"; + case "code-point": + return "Unicode code points"; + case "utf8-byte": + return "UTF-8 bytes"; + case "byte": + return "Bytes"; + default: + return "Awaiting first engine result"; + } +} + +export function CapabilityPanel({ + syntax, + execution, +}: { + readonly syntax?: RegexSyntaxResult; + readonly execution?: RegexExecutionResult; +}) { + const capabilities = execution?.engine.capabilities; + const rows = [ + [ + "Flavour", + execution?.engine.flavour ?? + syntax?.root.support.flavour ?? + "ECMAScript (awaiting workers)", + ], + [ + "Syntax provider", + syntax + ? `${syntax.provider.id} ${syntax.provider.version}` + : `regexpp ${REGEXPP_VERSION} (awaiting syntax worker)`, + ], + ["Syntax profile", SYNTAX_PROFILE], + [ + "Provider coverage", + syntax + ? `${syntax.coverage.status}: ${syntax.coverage.summary}` + : "Awaiting syntax worker result", + ], + [ + "Execution engine", + execution?.engine.engineName ?? "Awaiting first engine result", + ], + [ + "Engine/runtime version", + execution?.engine.engineVersion ?? "Shown after first execution", + ], + ["Native offsets", offsetLabel(execution?.engine.offsetUnit)], + ["Compilation", capability(capabilities, "compilation")], + ["Matching", capability(capabilities, "matching")], + ["Replacement", capability(capabilities, "replacement")], + ["Named captures", capability(capabilities, "namedCaptures")], + [ + "Capture history", + capability( + capabilities, + "captureHistory", + "the adapter exposes only the final retained capture", + ), + ], + [ + "Actual execution trace", + capability( + capabilities, + "actualTrace", + "the adapter exposes no engine trace", + ), + ], + ["Benchmark", capability(capabilities, "benchmark")], + ] as const; + return ( +
+
+
+

Current provider and adapter metadata

+

Capabilities

+
+ Community build +
+
+ {rows.map(([term, value]) => ( +
+
{term}
+
{value}
+
+ ))} +
+

+ Next flavour: official PCRE2 10.47 WebAssembly with actual callout + traces. Python, Go, Rust, .NET and Java remain unavailable until their + named runtimes pass the same worker, offset, conformance and licensing + gates. +

+
+ ); +} diff --git a/src/components/CaptureTable.test.tsx b/src/components/CaptureTable.test.tsx new file mode 100644 index 0000000..669229f --- /dev/null +++ b/src/components/CaptureTable.test.tsx @@ -0,0 +1,236 @@ +import { render, screen, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import type { CaptureRow } from "../regex/extraction/capture-rows"; +import { CaptureTable } from "./CaptureTable"; + +const rows: readonly CaptureRow[] = [ + { + id: "row-2-1", + matchNumber: 2, + groupNumber: 1, + groupName: "letter", + status: "participated", + value: "a", + start: 2, + end: 3, + length: 1, + nativeStart: 2, + nativeEnd: 3, + nativeUnit: "utf16", + line: 1, + column: 3, + }, + { + id: "row-1-1", + matchNumber: 1, + groupNumber: 1, + groupName: "emoji", + status: "participated", + value: "😀", + start: 0, + end: 2, + length: 2, + nativeStart: 0, + nativeEnd: 2, + nativeUnit: "utf16", + line: 1, + column: 1, + }, +]; + +afterEach(() => { + Reflect.deleteProperty(document, "execCommand"); +}); + +describe("CaptureTable", () => { + it("sorts, exposes Unicode offsets and copies values and rows separately", async () => { + const writeText = vi.fn().mockResolvedValue(undefined); + const user = userEvent.setup(); + Object.defineProperty(navigator, "clipboard", { + configurable: true, + value: { writeText }, + }); + render( + undefined} />, + ); + + const body = screen + .getByRole("grid", { + name: "Captured matches", + }) + .querySelector("tbody"); + if (!body) throw new Error("Capture table body is missing"); + expect(within(body).getAllByRole("row")[0]).toHaveTextContent("1emoji"); + + await user.click(screen.getByRole("button", { name: "Ascending" })); + const firstDescendingRow = within(body).getAllByRole("row").at(0); + if (!firstDescendingRow) throw new Error("Capture row is missing"); + expect( + within(firstDescendingRow).getAllByRole("cell")[0], + ).toHaveTextContent("2"); + expect( + within(firstDescendingRow).getAllByRole("cell")[2], + ).toHaveTextContent("letter"); + + await user.click(screen.getByLabelText("Unicode advanced view")); + expect( + screen.getByRole("columnheader", { name: "Start (code point)" }), + ).toBeInTheDocument(); + const emojiRow = within(body).getByRole("row", { + name: /Match 1, group 1, emoji/u, + }); + expect(emojiRow).toHaveTextContent("U+1F600"); + + await user.click( + screen.getByRole("button", { + name: "Copy value for match 1, group 1", + }), + ); + expect(writeText).toHaveBeenLastCalledWith("😀"); + expect(screen.getByRole("status")).toHaveTextContent( + /Copied the complete returned value/u, + ); + await user.click( + screen.getByRole("button", { + name: "Copy row for match 1, group 1", + }), + ); + expect(writeText).toHaveBeenLastCalledWith(JSON.stringify(rows[1])); + }); + + it("moves to a synchronized row page and reports a filtered selection", async () => { + const user = userEvent.setup(); + const pagedRows = Array.from({ length: 201 }, (_, index): CaptureRow => ({ + id: `row-${index + 1}-1`, + matchNumber: index + 1, + groupNumber: 1, + status: "participated", + value: `value-${index + 1}`, + start: index, + end: index + 1, + length: 1, + nativeStart: index, + nativeEnd: index + 1, + nativeUnit: "utf16", + line: 1, + column: index + 1, + })); + render( + undefined} + />, + ); + + expect(screen.getByText(/Page 2 of 2/u)).toBeInTheDocument(); + expect( + screen.getByRole("row", { + name: /Match 201, group 1, participated/u, + }), + ).toHaveAttribute("aria-selected", "true"); + + await user.type(screen.getByLabelText("Filter captures"), "missing"); + expect( + screen.getByText(/synchronized capture row is hidden/u), + ).toBeInTheDocument(); + }); + + it("disables capture export for an engine-limited result prefix", () => { + render( + undefined} + />, + ); + + expect( + screen.getByRole("button", { name: "Export all JSON" }), + ).toBeDisabled(); + expect( + screen.getByText(/Capture JSON export is disabled/u), + ).toBeInTheDocument(); + }); + + it("bounds only the visible capture value while copying the complete value", async () => { + const writeText = vi.fn().mockResolvedValue(undefined); + const user = userEvent.setup(); + Object.defineProperty(navigator, "clipboard", { + configurable: true, + value: { writeText }, + }); + const value = `${"\n".repeat(600)}tail`; + const row = { + ...rows[0]!, + value, + end: value.length, + length: value.length, + }; + render( + undefined} />, + ); + + expect(screen.getByRole("cell", { name: /UI preview/u })).toHaveTextContent( + `${value.length.toLocaleString()} UTF-16 units total; UI preview`, + ); + expect( + screen.getByRole("cell", { name: /UI preview/u }).textContent, + ).not.toContain("tail"); + await user.click( + screen.getByRole("button", { + name: "Copy value for match 2, group 1", + }), + ); + expect(writeText).toHaveBeenLastCalledWith(value); + }); + + it("labels a clipped engine value with the exact range length", () => { + const value = "x".repeat(600); + const row: CaptureRow = { + ...rows[0]!, + status: "truncated", + value, + start: 0, + end: 70_000, + length: 70_000, + }; + render( + undefined} />, + ); + + expect( + screen.getByRole("cell", { name: /engine value prefix/u }), + ).toHaveTextContent( + /engine value prefix: 600 of 70,000 UTF-16 units; UI shows the first 512/u, + ); + }); + + it("reports unavailable clipboard access instead of rejecting silently", async () => { + const user = userEvent.setup(); + Object.defineProperty(navigator, "clipboard", { + configurable: true, + value: undefined, + }); + Object.defineProperty(document, "execCommand", { + configurable: true, + value: vi.fn().mockReturnValue(false), + }); + render( + undefined} />, + ); + + await user.click( + screen.getByRole("button", { + name: "Copy value for match 1, group 1", + }), + ); + + expect(await screen.findByRole("alert")).toHaveTextContent( + /Clipboard access is unavailable/u, + ); + }); +}); diff --git a/src/components/CaptureTable.tsx b/src/components/CaptureTable.tsx new file mode 100644 index 0000000..c3f57c6 --- /dev/null +++ b/src/components/CaptureTable.tsx @@ -0,0 +1,551 @@ +import { useMemo, useState, type KeyboardEvent } from "react"; +import type { CaptureRow } from "../regex/extraction/capture-rows"; +import { writeClipboardText } from "../browser/clipboard"; + +const PAGE_SIZE = 200; +const MAXIMUM_CODE_POINTS_SHOWN = 64; +const MAXIMUM_VALUE_PREVIEW_UTF16 = 512; + +type SortKey = + | "matchNumber" + | "groupNumber" + | "groupName" + | "status" + | "value" + | "start" + | "end" + | "length" + | "line" + | "nativeStart"; + +const SORT_OPTIONS: readonly { + readonly value: SortKey; + readonly label: string; +}[] = [ + { value: "matchNumber", label: "Match" }, + { value: "groupNumber", label: "Group number" }, + { value: "groupName", label: "Group name" }, + { value: "status", label: "Status" }, + { value: "value", label: "Value" }, + { value: "start", label: "UTF-16 start" }, + { value: "end", label: "UTF-16 end" }, + { value: "length", label: "Length" }, + { value: "line", label: "Line" }, + { value: "nativeStart", label: "Native start" }, +]; + +function visibleWhitespace(value: string): string { + return value + .replaceAll(" ", "·") + .replaceAll("\t", "⇥") + .replaceAll("\r", "␍") + .replaceAll("\n", "↵\n"); +} + +function visibleValue(row: CaptureRow, showWhitespace: boolean): string { + const value = row.value; + if (value === undefined) return "—"; + const visible = value.slice(0, MAXIMUM_VALUE_PREVIEW_UTF16); + const rendered = showWhitespace ? visibleWhitespace(visible) : visible; + const uiTruncated = value.length > MAXIMUM_VALUE_PREVIEW_UTF16; + if (row.status === "truncated") { + return `${rendered}${uiTruncated ? "…" : ""} (engine value prefix: ${value.length.toLocaleString()} of ${row.length?.toLocaleString() ?? "unknown"} UTF-16 units${uiTruncated ? "; UI shows the first 512" : ""})`; + } + return uiTruncated + ? `${rendered}… (${(row.length ?? value.length).toLocaleString()} UTF-16 units total; UI preview)` + : rendered; +} + +function download(text: string, fileName: string, type: string): void { + const url = URL.createObjectURL(new Blob([text], { type })); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = fileName; + anchor.click(); + window.setTimeout(() => URL.revokeObjectURL(url), 1_000); +} + +function compareValues( + left: string | number | undefined, + right: string | number | undefined, +): number { + if (left === right) return 0; + if (left === undefined) return 1; + if (right === undefined) return -1; + return typeof left === "number" && typeof right === "number" + ? left - right + : String(left).localeCompare(String(right)); +} + +function codePointOffsets( + subject: string, + requestedOffsets: readonly number[], +): ReadonlyMap { + const pending = [...new Set(requestedOffsets)] + .filter((offset) => offset >= 0 && offset <= subject.length) + .sort((left, right) => left - right); + const result = new Map(); + let pendingIndex = 0; + let utf16Offset = 0; + let codePointOffset = 0; + + while (pendingIndex < pending.length) { + const target = pending[pendingIndex]; + if (target === undefined) break; + if (target === utf16Offset) { + result.set(target, codePointOffset); + pendingIndex += 1; + continue; + } + if (target < utf16Offset || utf16Offset >= subject.length) { + pendingIndex += 1; + continue; + } + const first = subject.charCodeAt(utf16Offset); + const second = subject.charCodeAt(utf16Offset + 1); + const width = + first >= 0xd800 && first <= 0xdbff && second >= 0xdc00 && second <= 0xdfff + ? 2 + : 1; + utf16Offset += width; + codePointOffset += 1; + } + return result; +} + +function visibleCodePoints(value: string | undefined): string { + if (value === undefined) return "—"; + const codePoints = Array.from(value); + const rendered = codePoints + .slice(0, MAXIMUM_CODE_POINTS_SHOWN) + .map((item) => { + const point = item.codePointAt(0); + return point === undefined + ? "U+?" + : `U+${point.toString(16).toUpperCase().padStart(4, "0")}`; + }); + return `${rendered.join(" ")}${ + codePoints.length > rendered.length + ? ` … (${codePoints.length.toLocaleString()} total)` + : "" + }`; +} + +export function CaptureTable({ + rows, + subject, + sourceResultTruncated = false, + selectedId, + onSelect, +}: { + readonly rows: readonly CaptureRow[]; + readonly subject: string; + readonly sourceResultTruncated?: boolean; + readonly selectedId?: string; + readonly onSelect: (row: CaptureRow) => void; +}) { + const [filter, setFilter] = useState(""); + const [page, setPage] = useState(0); + const [showWhitespace, setShowWhitespace] = useState(false); + const [showAdvanced, setShowAdvanced] = useState(false); + const [sortKey, setSortKey] = useState("matchNumber"); + const [sortDescending, setSortDescending] = useState(false); + const [copyNotice, setCopyNotice] = useState<{ + readonly kind: "success" | "error"; + readonly message: string; + }>(); + const filtered = useMemo(() => { + const needle = filter.toLocaleLowerCase(); + if (!needle) return rows; + return rows.filter((row) => + [ + row.matchNumber, + row.groupNumber, + row.groupName ?? "", + row.status, + row.value ?? "", + ] + .join(" ") + .toLocaleLowerCase() + .includes(needle), + ); + }, [filter, rows]); + const sorted = useMemo( + () => + filtered + .map((row, index) => ({ row, index })) + .sort((left, right) => { + const comparison = compareValues( + left.row[sortKey], + right.row[sortKey], + ); + return ( + (sortDescending ? -comparison : comparison) || + left.index - right.index + ); + }) + .map(({ row }) => row), + [filtered, sortDescending, sortKey], + ); + const selectedIndex = + selectedId === undefined + ? -1 + : sorted.findIndex((row) => row.id === selectedId); + const selectedPage = + selectedIndex < 0 ? undefined : Math.floor(selectedIndex / PAGE_SIZE); + const selectionPageKey = + selectedId === undefined + ? undefined + : `${selectedId}:${selectedPage ?? -1}`; + const [synchronizedSelectionPageKey, setSynchronizedSelectionPageKey] = + useState(); + if (selectionPageKey !== synchronizedSelectionPageKey) { + setSynchronizedSelectionPageKey(selectionPageKey); + if (selectedPage !== undefined) setPage(selectedPage); + } + const maximumPage = Math.max(0, Math.ceil(sorted.length / PAGE_SIZE) - 1); + const activePage = Math.min(page, maximumPage); + const visible = sorted.slice( + activePage * PAGE_SIZE, + (activePage + 1) * PAGE_SIZE, + ); + const visibleCodePointOffsets = useMemo( + () => + showAdvanced + ? codePointOffsets( + subject, + visible.flatMap((row) => [ + ...(row.start === undefined ? [] : [row.start]), + ...(row.end === undefined ? [] : [row.end]), + ]), + ) + : new Map(), + [showAdvanced, subject, visible], + ); + const selectedFilteredOut = + selectedId !== undefined && + rows.some((row) => row.id === selectedId) && + !sorted.some((row) => row.id === selectedId); + + const copyRow = async (row: CaptureRow): Promise => { + try { + await writeClipboardText(JSON.stringify(row)); + setCopyNotice({ + kind: "success", + message: `Copied match ${row.matchNumber}, group ${row.groupNumber} row JSON.`, + }); + } catch (error) { + setCopyNotice({ + kind: "error", + message: error instanceof Error ? error.message : String(error), + }); + } + }; + const copyValue = async (row: CaptureRow): Promise => { + if (row.value === undefined) return; + try { + await writeClipboardText(row.value); + setCopyNotice({ + kind: "success", + message: + row.status === "truncated" + ? `Copied the available engine value prefix for match ${row.matchNumber}, group ${row.groupNumber}.` + : `Copied the complete returned value for match ${row.matchNumber}, group ${row.groupNumber}.`, + }); + } catch (error) { + setCopyNotice({ + kind: "error", + message: error instanceof Error ? error.message : String(error), + }); + } + }; + + const moveRowFocus = ( + event: KeyboardEvent, + row: CaptureRow, + ) => { + if (event.target !== event.currentTarget) return; + const body = event.currentTarget.closest("tbody"); + const rows = body + ? Array.from(body.querySelectorAll("tr")) + : []; + const index = rows.indexOf(event.currentTarget); + let target: number; + + switch (event.key) { + case "ArrowDown": + target = Math.min(rows.length - 1, index + 1); + break; + case "ArrowUp": + target = Math.max(0, index - 1); + break; + case "Home": + target = 0; + break; + case "End": + target = rows.length - 1; + break; + case "Enter": + case " ": + event.preventDefault(); + onSelect(row); + return; + default: + return; + } + + event.preventDefault(); + const next = rows[target]; + next?.focus(); + next?.click(); + }; + + return ( +
+
+
+

Compact result view

+

Capture table

+
+ + {filtered.length} row{filtered.length === 1 ? "" : "s"} + +
+
+ + + + + + +
+ {sourceResultTruncated ? ( +

+ The engine stopped collecting results at its configured match or + capture-row limit. Capture JSON export is disabled because these rows + are only a returned prefix. +

+ ) : null} + {selectedFilteredOut ? ( +

+ The synchronized capture row is hidden by the current filter. Clear + the filter to show it. +

+ ) : null} + {copyNotice ? ( +

+ {copyNotice.message} +

+ ) : null} +
+ + + + + + + + + + + + + + + + {showAdvanced ? ( + <> + + + + + ) : null} + + + + + {visible.map((row) => ( + { + event.currentTarget.focus(); + onSelect(row); + }} + onKeyDown={(event) => moveRowFocus(event, row)} + > + + + + + + + + + + + + + {showAdvanced ? ( + <> + + + + + ) : null} + + + ))} + +
MatchGroupNameStatusValueStart (UTF-16)End (UTF-16)Length (UTF-16)Line:columnNative startNative endNative unitStart (code point)End (code point)Value code points + Actions +
{row.matchNumber}{row.groupNumber}{row.groupName ?? "—"}{row.status.replaceAll("-", " ")} + {visibleValue(row, showWhitespace)} + {row.start ?? "—"}{row.end ?? "—"}{row.length ?? "—"} + {row.line === undefined ? "—" : `${row.line}:${row.column}`} + {row.nativeStart ?? "—"}{row.nativeEnd ?? "—"}{row.nativeUnit ?? "—"} + {row.start === undefined + ? "—" + : (visibleCodePointOffsets.get(row.start) ?? + "split surrogate")} + + {row.end === undefined + ? "—" + : (visibleCodePointOffsets.get(row.end) ?? + "split surrogate")} + + {visibleCodePoints(row.value)} + + + + + +
+
+ {filtered.length > PAGE_SIZE ? ( + + ) : null} +
+ ); +} diff --git a/src/components/DiagnosticsPanel.tsx b/src/components/DiagnosticsPanel.tsx new file mode 100644 index 0000000..7c29df5 --- /dev/null +++ b/src/components/DiagnosticsPanel.tsx @@ -0,0 +1,42 @@ +import type { RegexDiagnostic } from "../regex/model/diagnostics"; + +export function DiagnosticsPanel({ + diagnostics, + onSelect, +}: { + readonly diagnostics: readonly RegexDiagnostic[]; + readonly onSelect: (diagnostic: RegexDiagnostic) => void; +}) { + return ( +
+
+

Diagnostics

+ {diagnostics.length} +
+ {diagnostics.length === 0 ? ( +

No current diagnostics.

+ ) : ( +
    + {diagnostics.map((diagnostic) => ( +
  • + +
  • + ))} +
+ )} +
+ ); +} diff --git a/src/components/ExplanationTree.test.tsx b/src/components/ExplanationTree.test.tsx new file mode 100644 index 0000000..be60205 --- /dev/null +++ b/src/components/ExplanationTree.test.tsx @@ -0,0 +1,94 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import type { NormalizedRegexNode } from "../regex/model/syntax"; +import { ExplanationTree } from "./ExplanationTree"; + +function node( + id: string, + children: readonly NormalizedRegexNode[] = [], +): NormalizedRegexNode { + return { + id, + kind: "literal", + range: { startUtf16: 0, endUtf16: 1 }, + raw: id, + explanation: `Literal ${id}.`, + children, + properties: { + zeroWidth: false, + nullable: false, + minimumLength: 1, + maximumLength: 1, + consumesInput: true, + }, + support: { + flavour: "ecmascript", + status: "supported", + notes: [], + }, + provenance: { + provider: "test", + providerVersion: "1", + source: "parsed", + }, + }; +} + +describe("ExplanationTree", () => { + it("bounds a large preview while retaining a roving-tabindex fallback", () => { + const children = Array.from({ length: 2_001 }, (_, index) => + node(`child-${index}`), + ); + const root = node("root", children); + const { container } = render( + , + ); + + expect(screen.getByTestId("explanation-render-limit")).toHaveTextContent( + "Showing the first 2,000 of 2,002 normalized syntax nodes.", + ); + expect(screen.getByTestId("explanation-render-limit")).toHaveTextContent( + "The selected pattern node is outside this bounded tree preview.", + ); + expect(container.querySelectorAll('[role="treeitem"]')).toHaveLength(2_000); + expect( + container.querySelectorAll('[role="treeitem"][tabindex="0"]'), + ).toHaveLength(1); + expect( + container.querySelector('[role="treeitem"][tabindex="0"] .tree-token'), + ).toHaveTextContent("root"); + expect(root.children).toHaveLength(2_001); + }); + + it("bounds descendants when the cutoff occurs below an unchanged ancestor", () => { + const nested = node( + "nested", + Array.from({ length: 2_100 }, (_, index) => node(`child-${index}`)), + ); + const root = node("root", [nested]); + const { container } = render( + , + ); + + expect(container.querySelectorAll('[role="treeitem"]')).toHaveLength(2_000); + expect(root.children[0]?.children).toHaveLength(2_100); + }); + + it("shows normalized semantics, active flags, compatibility and provenance", () => { + render( + , + ); + + expect(screen.getByRole("treeitem")).toHaveTextContent( + /range: 0…1 UTF-16.*consumes: yes.*empty: no.*length: 1…1.*active flags: gu.*flavour: ecmascript · supported.*compatibility: no parser warning.*risk: not assessed.*parser: test 1 · parsed/u, + ); + }); +}); diff --git a/src/components/ExplanationTree.tsx b/src/components/ExplanationTree.tsx new file mode 100644 index 0000000..8220ba3 --- /dev/null +++ b/src/components/ExplanationTree.tsx @@ -0,0 +1,229 @@ +import { useMemo } from "react"; +import type { NormalizedRegexNode } from "../regex/model/syntax"; +import { handleTreeNavigation } from "./tree-navigation"; + +const MAXIMUM_RENDERED_EXPLANATION_NODES = 2_000; + +export interface ExplanationTreeProps { + readonly root?: NormalizedRegexNode; + readonly selectedId?: string; + readonly flags?: readonly string[]; + readonly onSelect: (node: NormalizedRegexNode) => void; +} + +function countNodes(root: NormalizedRegexNode): number { + let count = 0; + const pending = [root]; + while (pending.length > 0) { + const node = pending.pop(); + if (!node) continue; + count += 1; + for (const child of node.children) pending.push(child); + } + return count; +} + +function boundedRoot( + root: NormalizedRegexNode, + maximum: number, +): NormalizedRegexNode { + let remaining = maximum; + const takeNode = ( + node: NormalizedRegexNode, + ): NormalizedRegexNode | undefined => { + if (remaining <= 0) return undefined; + remaining -= 1; + const children: NormalizedRegexNode[] = []; + for (const child of node.children) { + const visible = takeNode(child); + if (!visible) break; + children.push(visible); + } + return { ...node, children }; + }; + return takeNode(root) ?? root; +} + +function containsNode(root: NormalizedRegexNode, id: string): boolean { + const pending = [root]; + while (pending.length > 0) { + const node = pending.pop(); + if (!node) continue; + if (node.id === id) return true; + for (const child of node.children) pending.push(child); + } + return false; +} + +function lengthLabel(node: NormalizedRegexNode): string { + const minimum = node.properties.minimumLength; + const maximum = node.properties.maximumLength; + if (minimum === undefined && maximum === undefined) return "length unknown"; + return `${minimum ?? "?"}…${maximum === null ? "∞" : (maximum ?? "?")}`; +} + +function consumesInputLabel( + value: NormalizedRegexNode["properties"]["consumesInput"], +): string { + return value === true ? "yes" : value === false ? "no" : value; +} + +function nullableLabel( + value: NormalizedRegexNode["properties"]["nullable"], +): string { + return value === true ? "yes" : value === false ? "no" : value; +} + +function ExplanationNode({ + node, + selectedId, + onSelect, + depth, + focusableId, + flags, +}: { + readonly node: NormalizedRegexNode; + readonly selectedId?: string; + readonly onSelect: (node: NormalizedRegexNode) => void; + readonly depth: number; + readonly focusableId: string; + readonly flags: readonly string[]; +}) { + const compatibility = + node.support.notes.length > 0 + ? node.support.notes.slice(0, 2).join("; ").slice(0, 240) + : "no parser warning"; + return ( +
  • + + {node.children.length > 0 ? ( +
      + {node.children.map((child) => ( + + ))} +
    + ) : null} +
  • + ); +} + +export function ExplanationTree({ + root, + selectedId, + flags = [], + onSelect, +}: ExplanationTreeProps) { + const totalNodes = useMemo(() => (root ? countNodes(root) : 0), [root]); + const visibleRoot = useMemo( + () => + root ? boundedRoot(root, MAXIMUM_RENDERED_EXPLANATION_NODES) : undefined, + [root], + ); + const renderedNodes = Math.min( + totalNodes, + MAXIMUM_RENDERED_EXPLANATION_NODES, + ); + const selectedNodeVisible = + selectedId === undefined || + visibleRoot === undefined || + containsNode(visibleRoot, selectedId); + const focusableId = + selectedId && selectedNodeVisible ? selectedId : (visibleRoot?.id ?? ""); + + return ( +
    +
    +
    +

    Pattern structure

    +

    Explanation tree

    +
    + + Deterministic · syntax provider + +
    + {totalNodes > renderedNodes ? ( +

    + Tree preview limited. Showing the first{" "} + {renderedNodes.toLocaleString()} of {totalNodes.toLocaleString()}{" "} + normalized syntax nodes. The complete normalized syntax tree remains + available to the application. + {!selectedNodeVisible + ? " The selected pattern node is outside this bounded tree preview." + : ""} +

    + ) : null} + {visibleRoot ? ( +
      + +
    + ) : ( +

    The syntax worker is preparing the tree.

    + )} +
    + ); +} diff --git a/src/components/ExtractionTree.test.tsx b/src/components/ExtractionTree.test.tsx new file mode 100644 index 0000000..0c48cd3 --- /dev/null +++ b/src/components/ExtractionTree.test.tsx @@ -0,0 +1,76 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import type { ExtractionNode } from "../regex/model/match"; +import { ExtractionTree } from "./ExtractionTree"; + +function node( + id: string, + children: readonly ExtractionNode[] = [], +): ExtractionNode { + return { + id, + kind: "capture", + label: id, + value: id, + status: "participated", + children, + provenance: "engine-result", + }; +} + +describe("ExtractionTree", () => { + it("bounds descendants when the cutoff occurs below an ancestor", () => { + const nested = node( + "nested", + Array.from({ length: 2_100 }, (_, index) => node(`child-${index}`)), + ); + const roots = [node("root", [nested])]; + const { container } = render( + , + ); + + expect(container.querySelectorAll('[role="treeitem"]')).toHaveLength(2_000); + expect(roots[0]?.children[0]?.children).toHaveLength(2_100); + }); + + it("labels UI-only value truncation without changing engine status", () => { + const value = "x".repeat(101); + render( + , + ); + + expect(screen.getByRole("treeitem")).toHaveTextContent( + /… \(101 UTF-16 units total; UI preview\).*participated/u, + ); + }); + + it("distinguishes a bounded engine prefix from the exact capture range", () => { + render( + , + ); + + expect(screen.getByRole("treeitem")).toHaveTextContent( + /engine value prefix: 101 of 70,000 UTF-16 units; UI shows the first 100.*UTF-16 range: 10…70010.*native range: 10…70010 utf16/u, + ); + }); +}); diff --git a/src/components/ExtractionTree.tsx b/src/components/ExtractionTree.tsx new file mode 100644 index 0000000..d4ce419 --- /dev/null +++ b/src/components/ExtractionTree.tsx @@ -0,0 +1,223 @@ +import { useMemo } from "react"; +import type { ExtractionNode } from "../regex/model/match"; +import { handleTreeNavigation } from "./tree-navigation"; + +const MAXIMUM_RENDERED_EXTRACTION_NODES = 2_000; +const MAXIMUM_VALUE_PREVIEW_UTF16 = 100; + +export interface ExtractionTreeProps { + readonly nodes: readonly ExtractionNode[]; + readonly selectedId?: string; + readonly onSelect: (node: ExtractionNode) => void; + readonly repeatedCaptureNumbers: ReadonlySet; +} + +function printable(node: ExtractionNode): string { + const value = node.value; + if (value === undefined) return "—"; + if (value === "") return "∅ empty"; + const visible = value + .slice(0, MAXIMUM_VALUE_PREVIEW_UTF16) + .replaceAll("\n", "↵") + .replaceAll("\r", "␍") + .replaceAll("\t", "⇥"); + const exactLength = node.range + ? node.range.endUtf16 - node.range.startUtf16 + : undefined; + const uiTruncated = value.length > MAXIMUM_VALUE_PREVIEW_UTF16; + if (node.status === "truncated") { + return `${visible}${uiTruncated ? "…" : ""} (engine value prefix: ${value.length.toLocaleString()} of ${exactLength?.toLocaleString() ?? "unknown"} UTF-16 units${uiTruncated ? "; UI shows the first 100" : ""})`; + } + return uiTruncated + ? `${visible}… (${(exactLength ?? value.length).toLocaleString()} UTF-16 units total; UI preview)` + : visible; +} + +function countNodes(nodes: readonly ExtractionNode[]): number { + let count = 0; + const pending = [...nodes]; + while (pending.length > 0) { + const node = pending.pop(); + if (!node) continue; + count += 1; + pending.push(...node.children); + } + return count; +} + +function boundedNodes( + nodes: readonly ExtractionNode[], + maximum: number, +): readonly ExtractionNode[] { + let remaining = maximum; + const takeNode = (node: ExtractionNode): ExtractionNode | undefined => { + if (remaining <= 0) return undefined; + remaining -= 1; + const children: ExtractionNode[] = []; + for (const child of node.children) { + const visible = takeNode(child); + if (!visible) break; + children.push(visible); + } + return { ...node, children }; + }; + const visible: ExtractionNode[] = []; + for (const node of nodes) { + const bounded = takeNode(node); + if (!bounded) break; + visible.push(bounded); + } + return visible; +} + +function containsNode(nodes: readonly ExtractionNode[], id: string): boolean { + const pending = [...nodes]; + while (pending.length > 0) { + const node = pending.pop(); + if (!node) continue; + if (node.id === id) return true; + pending.push(...node.children); + } + return false; +} + +function ExtractionTreeNode({ + node, + selectedId, + onSelect, + repeatedCaptureNumbers, + depth, + focusableId, +}: { + readonly node: ExtractionNode; + readonly selectedId?: string; + readonly onSelect: (node: ExtractionNode) => void; + readonly repeatedCaptureNumbers: ReadonlySet; + readonly depth: number; + readonly focusableId: string; +}) { + const repeatedFinal = + node.groupNumber !== undefined && + repeatedCaptureNumbers.has(node.groupNumber); + return ( +
  • + + {repeatedFinal ? ( +

    + This engine exposes only the final retained capture for this repeated + group. +

    + ) : null} + {node.children.length > 0 ? ( +
      + {node.children.map((child) => ( + + ))} +
    + ) : null} +
  • + ); +} + +export function ExtractionTree({ + nodes, + selectedId, + onSelect, + repeatedCaptureNumbers, +}: ExtractionTreeProps) { + const totalNodes = useMemo(() => countNodes(nodes), [nodes]); + const visibleNodes = useMemo( + () => boundedNodes(nodes, MAXIMUM_RENDERED_EXTRACTION_NODES), + [nodes], + ); + const renderedNodes = Math.min(totalNodes, MAXIMUM_RENDERED_EXTRACTION_NODES); + const selectedNodeVisible = + selectedId === undefined || containsNode(visibleNodes, selectedId); + const focusableId = + selectedId && selectedNodeVisible + ? selectedId + : (visibleNodes[0]?.id ?? ""); + return ( +
    +
    +
    +

    Engine results

    +

    Extraction tree

    +
    + Actual engine result +
    + {totalNodes > renderedNodes ? ( +

    + Tree preview limited. Showing the first{" "} + {renderedNodes.toLocaleString()} of {totalNodes.toLocaleString()}{" "} + returned result nodes. All returned data remains available to the + other result views and exports. + {!selectedNodeVisible + ? " The selected result is outside this bounded tree preview." + : ""} +

    + ) : null} + {nodes.length > 0 ? ( +
      + {visibleNodes.map((node) => ( + + ))} +
    + ) : ( +

    No matches to extract.

    + )} +
    + ); +} diff --git a/src/components/HelpDialog.tsx b/src/components/HelpDialog.tsx new file mode 100644 index 0000000..0ad9934 --- /dev/null +++ b/src/components/HelpDialog.tsx @@ -0,0 +1,151 @@ +import { useEffect, useRef } from "react"; +import { DEFAULT_REGEX_LIMITS } from "../regex/execution/request-limits"; +import { manifest } from "../toolbox/manifest"; + +const releaseSourceUrl = `https://git.add-ideas.de/zemion/regex-tools/src/tag/v${manifest.version}`; + +export function HelpDialog({ + open, + onClose, +}: { + readonly open: boolean; + readonly onClose: () => void; +}) { + const dialog = useRef(null); + useEffect(() => { + const element = dialog.current; + if (!element) return; + if (open && !element.open) element.showModal(); + if (!open && element.open) element.close(); + }, [open]); + return ( + +
    +
    +

    Regex Tools {manifest.version}

    +

    Local regex workbench

    +
    + +
    +
    +
    +

    What runs where

    +

    + Syntax parsing and actual ECMAScript execution run in separate, + killable browser workers. Patterns, subjects and projects are never + uploaded. There is no backend, account, telemetry, CDN dependency, + or executable replacement function. +

    +
    +
    +

    Trees and replacement mapping

    +

    + The explanation tree is deterministic structure derived from the + syntax provider. The extraction tree contains actual browser-engine + matches and captures. Replacement mode adds a typed token tree and + bounded per-match contribution/range mapping over those actual + matches. None is an internal V8, SpiderMonkey or JavaScriptCore + execution trace. +

    +
    +
    +

    Timeouts and limits

    +
    +
    +
    Live execution
    +
    {DEFAULT_REGEX_LIMITS.liveExecutionTimeoutMs} ms
    +
    +
    +
    Manual execution
    +
    {DEFAULT_REGEX_LIMITS.manualExecutionTimeoutMs} ms
    +
    +
    +
    Maximum matches
    +
    {DEFAULT_REGEX_LIMITS.maximumMatches.toLocaleString()}
    +
    +
    +
    Maximum capture rows
    +
    + {DEFAULT_REGEX_LIMITS.maximumCaptureRows.toLocaleString()} +
    +
    +
    +
    Maximum capture groups
    +
    + {DEFAULT_REGEX_LIMITS.maximumCaptureGroups.toLocaleString()} +
    +
    +
    +
    Replacement template
    +
    + {DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16.toLocaleString()}{" "} + UTF-16 units +
    +
    +
    +
    List template
    +
    + {DEFAULT_REGEX_LIMITS.maximumListTemplateUtf16.toLocaleString()}{" "} + UTF-16 units +
    +
    +
    +
    Unit-test suite wall time
    +
    60 seconds
    +
    +
    +

    + A timeout terminates the worker. It means “timed out”, never “no + match”. The following run uses a fresh worker. +

    +
    +
    +

    Persistence

    +

    + Ad-hoc test text is excluded from local saves and exports by + default. Test definitions are deliberate project data. Import is + validated and paused until you review and run it. +

    +
    +
    +

    Legal notice

    +

    + Copyright © 2026 Albrecht Degering. Regex Tools is free software: + you may redistribute it and/or modify it under the terms of the GNU + General Public License, version 3 or (at your option) any later + version. It comes without any warranty, to the extent permitted by + law. +

    +

    + Read the{" "} + + complete licence + {" "} + or inspect and obtain the{" "} + + corresponding source code + + . +

    +
    +
    +
    + +
    +
    + ); +} diff --git a/src/components/ListPanel.tsx b/src/components/ListPanel.tsx new file mode 100644 index 0000000..7309c30 --- /dev/null +++ b/src/components/ListPanel.tsx @@ -0,0 +1,226 @@ +import { useMemo, useState } from "react"; +import { + exportListRows, + parseListTemplate, + renderListRows, + type ListRow, +} from "../regex/list/list-template"; +import type { RegexMatchResult } from "../regex/model/match"; +import { + DEFAULT_REGEX_LIMITS, + TEMPLATE_PRESENTATION_LIMITS, +} from "../regex/execution/request-limits"; + +function download(text: string, format: string): void { + const mediaTypes: Record = { + text: "text/plain;charset=utf-8", + csv: "text/csv;charset=utf-8", + json: "application/json", + ndjson: "application/x-ndjson", + }; + const extensions: Record = { + text: "txt", + csv: "csv", + json: "json", + ndjson: "ndjson", + }; + const url = URL.createObjectURL( + new Blob([text], { type: mediaTypes[format] }), + ); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = `regex-extraction.${extensions[format]}`; + anchor.click(); + window.setTimeout(() => URL.revokeObjectURL(url), 1_000); +} + +function ListPreview({ rows }: { readonly rows: readonly ListRow[] }) { + return rows.length > 0 ? ( +
      + {rows + .slice(0, TEMPLATE_PRESENTATION_LIMITS.listPreviewRows) + .map((row) => ( +
    1. + {row.value || "∅"} +
    2. + ))} +
    + ) : ( +

    No rows generated.

    + ); +} + +export function ListPanel({ + templateSource, + onTemplateChange, + matches, + subject, + sourceResultTruncated = false, +}: { + readonly templateSource: string; + readonly onTemplateChange: (value: string) => void; + readonly matches: readonly RegexMatchResult[]; + readonly subject: string; + readonly sourceResultTruncated?: boolean; +}) { + const [format, setFormat] = useState<"text" | "csv" | "json" | "ndjson">( + "text", + ); + const template = useMemo( + () => parseListTemplate(templateSource), + [templateSource], + ); + const rendered = useMemo( + () => + renderListRows( + template, + matches, + subject, + "test-text", + sourceResultTruncated, + ), + [matches, sourceResultTruncated, subject, template], + ); + const rows = rendered.rows; + const incompleteRows = rows.filter((row) => !row.complete); + const renderedTokenChips = template.tokens.slice( + 0, + TEMPLATE_PRESENTATION_LIMITS.listTokenChips, + ); + return ( +
    +
    +
    +
    +

    Typed, non-executable template

    +

    List / extraction template

    +
    +
    +