From aaa2f365488c31e22017a0360c630e9b0dde2b0f Mon Sep 17 00:00:00 2001 From: ACFHarbinger Date: Sat, 8 Aug 2026 10:49:29 +0100 Subject: [PATCH 01/27] Added license, research, and reports --- LICENSE | 661 ++++++++++++++++++ ...e Preferences and Advanced Technologies.md | 240 +++++++ ...eractive Portfolio Website Architecture.md | 190 +++++ 3 files changed, 1091 insertions(+) create mode 100644 LICENSE create mode 100644 docs/moon/reports/Website Preferences and Advanced Technologies.md create mode 100644 docs/moon/research/Global Interactive Portfolio Website Architecture.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/docs/moon/reports/Website Preferences and Advanced Technologies.md b/docs/moon/reports/Website Preferences and Advanced Technologies.md new file mode 100644 index 0000000..2e4cbaa --- /dev/null +++ b/docs/moon/reports/Website Preferences and Advanced Technologies.md @@ -0,0 +1,240 @@ +# **The Architecture of Digital Excellence: A Comprehensive Analysis of Website User Preferences, Ergonomics, and Advanced Web Technologies in 2026** + +The digital landscape has evolved into a hyper-competitive ecosystem where user attention is a scarce and highly contested resource. Modern website design requires a rigorous synthesis of psychological understanding, aesthetic precision, and advanced computational engineering. In 2026, the metrics by which a digital experience is judged extend far beyond mere functionality or basic aesthetic appeal. The intersection of human-computer interaction (HCI), rendering architectures, and cognitive ergonomics dictates user retention, conversion rates, and industry recognition. Furthermore, as the browser matures into a fully-fledged computational platform, the underlying engineering frameworks have shifted from simple document object model (DOM) manipulation to massively parallel graphics processing unit (GPU) computing and near-native binary execution. +This report provides an exhaustive analysis of the statistics governing user preferences, the ergonomic frameworks shaping usability, the current state of digital accessibility, the stringent criteria utilized by premier industry awards, and the frontier of web technologies available to computer scientists and engineers for building computationally impressive digital environments. + +## **The Empirical Landscape of User Preferences and Visual Aesthetics** + +The initial cognitive processing of a digital interface occurs in milliseconds, forming a durable impression that dictates all subsequent user behavior. Empirical data demonstrates that 94% of a user's first impression of a business is directly related to the aesthetic quality of its website1. Visitors form a preliminary opinion about a website's visual appeal in approximately 0.05 seconds, and 75% of consumers admit to making immediate judgments regarding a company's overall credibility based exclusively on web design1. If the layout is deemed visually unattractive, 38% of visitors will immediately abandon the page1. + +### **Minimalist Principles, Maximalist Applications, and Visual Hierarchy** + +By 2026, 83% of newly launched websites employ minimalist design principles, prioritizing clean styles and substantial negative space1. This shift is not merely stylistic but cognitive; clutter-free interfaces reduce cognitive overload, facilitate the rapid processing of information, and guide the user naturally toward key actions. A significant 85% of users express a preference for websites utilizing white or minimalistic background elements, while 60% specifically seek out layouts dominated by negative space1. +However, maximalism has emerged as a parallel trend in specific sectors. While minimalism works best when clarity, sophistication, and raw usability are the primary goals, maximalism is highly suited to industries where emotion, storytelling, and brand personality drive user engagement3. Regardless of the chosen aesthetic philosophy, the spatial distribution of visual attention remains highly predictable. Eye-tracking and behavioral studies confirm that 80% of user attention is concentrated on the upper half (above the fold) of web pages, and 67% of users prefer scanning content rather than reading long, unbroken paragraphs1. This necessitates a stringent visual hierarchy. Implementing headings sized two to three times larger than the body text improves structural comprehension by 40%, effectively guiding the eye through the intended narrative flow2. + +### **Color Psychology and Typographic Ergonomics** + +Color and typography act as subliminal behavioral modifiers. Strategic application of color palettes directly influences dwell time, brand perception, and conversion likelihood. The underlying mechanisms rely on human evolutionary psychology and established cultural associations. Table 1 summarizes the empirical impact of color choices on user behavior. + +| Color / Palette Strategy | Empirical Impact on User Behavior and Perception | +| :---- | :---- | +| **Black Text on White** | Yields the highest contrast ratio; preferred by 87% of users for standard reading2. | +| **Red Call-to-Action (CTA)** | Triggers urgency and attention, increasing click-through rates by 21% to 34%2. | +| **Gold / Premium Tones** | Associated with luxury; boosts premium product sales by up to 20%2. | +| **Blue / Green** | Evokes trust and stability; utilized in 25% of top-performing financial websites2. | +| **Orange** | Increases appetite and urgency, generating 20% more food orders in hospitality apps2. | +| **Pink** | Enhances feminine appeal, boosting female user engagement metrics by 15%2. | +| **Pastel Color Palettes** | Reduces user anxiety, resulting in 28% longer session durations2. | +| **Monochrome Schemes** | Unifies brand identity, improving brand recall and consistency by 22%2. | +| **Gradient Backgrounds** | Increases the perceived modernity of the digital interface by 45%2. | + +Typographic ergonomics are equally critical. While serif typography usage has seen an 18% increase in brand-forward, editorial-style sites for aesthetic differentiation, it is noted to reduce screen readability by 12% in dense text blocks1. Conversely, sans-serif fonts increase online reading speed by 20%2. Optimal typographic scaling dictates a 16px baseline font size, which boosts overall readability metrics by 25%, paired with a 1.5x line height to improve legibility by 15% and reduce tracking errors2. Furthermore, limiting the typographic palette to a maximum of three font families per site achieves visual harmony in 90% of use cases, while bounding text blocks to an optimal 600px line length reduces reading errors by an additional 20%2. The adoption of variable fonts, which consolidate multiple weights and styles into a single file, allows these typographic requirements to be met while loading 30% faster than traditional font stacks2. + +### **The Science of Dark Mode and Visual Strain** + +Dark mode has transitioned from an experimental feature to a foundational user expectation, preferred by 82% of users in low-light environments2. The physiological benefits include reduced eye strain during extended sessions and extended battery life on devices utilizing OLED screens, where black pixels are entirely deactivated3. +However, the implementation of dark mode requires nuanced optical understanding. For the approximately 50% of the population with uncorrected astigmatism, dark mode can inadvertently increase visual strain due to the "halation effect," where bright white text appears to bleed, blur, or halo into the surrounding black background4. Furthermore, despite widespread user assumptions, clinical studies in 2026 demonstrate no evidence that dark mode improves blink rates; users still experience "blink amnesia" during high-focus tasks, leading to tear film evaporation4. Consequently, dark mode interfaces must utilize softened contrast ratios (e.g., off-white text on dark gray backgrounds rather than pure white on pure black) to mitigate astigmatic halation while preserving low-light comfort. + +### **Media, Graphics, and Immersive Elements** + +The integration of media elements requires a delicate balance between engagement and performance degradation. Pages incorporating high-quality visuals generate 80% to 94% more views than text-only alternatives1. In e-commerce contexts, high-fidelity product images increase conversion likelihood by up to 60%, and sites utilizing personalized, context-aware imagery observe a 35% higher conversion rate compared to those relying on generic stock photography, which is still utilized by a surprising 40% of websites1. +Motion and depth are increasingly vital to user engagement, provided they are implemented with restraint. Subtle, micro-animated elements increase time-on-page by 25%, and 65% of users prefer subtle animations over entirely static screens1. Video backgrounds, while polarizing, are preferred by 62% of users and can increase engagement by 34%, though they introduce severe load-time penalties if not properly compressed1. To mitigate these penalties, 48% of modern sites utilize SVG graphics for resolution-independent icon clarity, which simultaneously reduces cognitive load by 28%1. Furthermore, WebP image compression allows raster assets to load up to 30% faster, saving 70% in file size compared to legacy formats1. + +## **Ergonomics, Usability, and Interaction Dynamics** + +Digital ergonomics represents the intersection of biomechanics, cognitive psychology, and user interface engineering. The fundamental laws of human-computer interaction govern how efficiently a user can navigate a system, process information, and execute desired actions. + +### **The Mathematics of Interaction: Hick-Hyman and Fitts's Laws** + +The Hick-Hyman Law postulates that the time required for a user to make a decision increases logarithmically with the number of available choices. The choice reaction time (![][image1]) is mathematically modeled by the entropy of the decision set: +![][image2] +where ![][image3] is the number of equiprobable choices, and ![][image4] and ![][image5] are empirically derived constants5. In web navigation, this dictates the necessity of robust information architecture. Mega menus leverage this principle by grouping choices logically into digestible clusters, thereby increasing conversions by 30%2. Similarly, breadcrumbs reduce navigational entropy, improving user satisfaction by 20%2. In forms, simplifying inputs to a maximum of three fields leverages this principle to improve submission rates by 60% by minimizing cognitive overload1. +For physical interaction, particularly on mobile devices, Fitts's Law dictates the time required to rapidly move a pointer (or finger) to a target area. The law defines this time as a function of the ratio between the distance to the target and the width of the target6. Ergonomic studies reveal that touch targets smaller than 48 pixels fail to register 70% of user taps accurately2. With 59% of global web traffic originating from mobile devices, accommodating Fitts's law via mobile-first design is a strict prerequisite1. Sites failing to adapt to these ergonomic realities suffer severe attrition: 50% of mobile users abandon non-responsive sites, 73% bounce if the interface does not scale correctly, and 79% abandon slow mobile sites outright1. + +### **Core Web Vitals and Performance Economics** + +Performance is intrinsically linked to usability and search engine visibility. The 2026 iteration of Google's Core Web Vitals (CWV) enforces exceptionally strict performance thresholds, acting as a primary ranking signal for organic search8. Table 2 outlines the 2026 CWV metrics and their respective thresholds. + +| Metric | Measurement Focus | 2026 "Good" Threshold | "Poor" Threshold | +| :---- | :---- | :---- | :---- | +| **Largest Contentful Paint (LCP)** | Visual load speed of the largest above-the-fold element. | Under 2.0 seconds | Over 4.0 seconds | +| **Interaction to Next Paint (INP)** | Overall responsiveness to user input across the entire session. | Under 200 milliseconds | Over 500 milliseconds | +| **Cumulative Layout Shift (CLS)** | Visual stability and unexpected layout shifts during rendering. | Under 0.1 | Over 0.25 | + +In March 2026, the LCP threshold for a "Good" rating was tightened from 2.5 seconds to 2.0 seconds8. Consequently, 47% of consumers now expect mobile load times under 2 seconds, and 40% will abandon a site if loading exceeds 3 seconds1. The economic impact is severe: every 100-millisecond delay in load time costs roughly 1% in sales, and a 1-second delay reduces overall conversions by 7%1. +The transition from First Input Delay (FID) to Interaction to Next Paint (INP) in March 2024 fundamentally altered how responsiveness is quantified8. While FID only measured the first interaction, INP captures the full latency from user input, through JavaScript processing, to the final visual update across the *entire* session10. INP is subdivided into three critical phases: + +> 1. **Input Delay:** The time before the browser begins executing event handlers. This is often inflated during initial page load when the main thread is blocked by JavaScript parsing. Data indicates interactions during the loading phase exhibit a p75 INP of 132ms, compared to just 50ms post-load10. +> 2. **Processing Time:** The duration the browser spends executing callbacks. Inefficient DOM operations, complex state updates, or synchronous third-party analytics calls inflate this metric10. +> 3. **Presentation Delay:** The time required to composite and render the subsequent visual frame10. + +Despite the known economic impacts of latency, HTTP Archive data from May 2026 reveals that only 49.1% of mobile websites passed all three Core Web Vitals, compared to 58.0% of desktop sites12. On mobile, LCP remains the primary bottleneck, while surprisingly, CLS is the weakest metric on desktop12. Platform choice also plays a role in these pass rates due to default architectural weights; Duda and Webflow achieve near 80% pass rates, while heavily templated WordPress and Magento sites average between 40% and 46%12. + +### **Micro-Interactions and Latency Perception Thresholds** + +The human perception of time in digital interfaces is non-linear. The brain categorizes latency into distinct psychological thresholds, necessitating precise calibration of micro-interactions to maintain the illusion of seamless user control13. + +* **10 ms – 16 ms (Smooth Animation):** Achieving 60 frames per second (FPS) requires a frame budget of 16.6ms. At 10ms, operations feel instantaneously connected to physical movement. Dropped frames in this window result in visual stutter and broken immersion13. +* **50 ms (Tactility):** The threshold at which haptic or visual feedback ceases to feel immediate and begins to resemble an echo. Interactions must respond within 50ms to feel physically connected to the user's touch13. +* **100 ms (Immediacy):** Simple feedback animations, such as toggling a switch, displaying a hover state, or confirming a button press, must complete within 100ms. This duration feels instantaneous while providing enough time for the visual cortex to register the change13. +* **200 ms – 300 ms (State Transitions):** Substantial screen changes, such as sliding in a modal window or opening a navigation drawer, require 200–300ms. Easing functions (e.g., ease-out deceleration curves) are critical here to mimic the physics of physical momentum14. +* **300+ ms (Hesitation/Uncertainty):** If a user hovers over an element for more than 300ms without interacting, it signals cognitive hesitation. Advanced telemetry utilizes this delay to trigger contextual tooltips, soft pulse animations, or supportive micro-cues to guide the user16. +* **330 ms (Debounce Duration):** The ideal debounce threshold to prevent double-firing inputs, perfectly timed to accommodate average mobile typing speeds of 38 words per minute13. + +A critical metric in modern UX engineering is the "3-Second Rule" for interface animation. If the cumulative duration of all sequential animations on a single page or during a specific task exceeds 3 seconds, users experience a loss of agency, feel held hostage by the interface, and perceive the system as sluggish, regardless of actual load times17. Furthermore, adaptive press detection can optimize perceived performance by calculating gesture velocity vectors; a fast touchscreen swipe (\>150px/s) is accepted immediately, while a slow gesture (\<50px/s) delays the press confirmation state by 100ms to prevent false triggers, reducing accidental taps by 41%16. + +## **The Accessibility Imperative: The 2026 WebAIM Analysis** + +Accessibility is a fundamental requirement of ethical software engineering and a strict vector for legal compliance, yet the internet remains overwhelmingly hostile to users with disabilities. Approximately 1.3 billion people globally experience a disability, controlling over $490 billion in disposable income in the US alone18. Despite this vast audience and growing legal pressure, the 2026 WebAIM Million report—a comprehensive analysis of the top one million homepages—revealed a systemic regression in accessibility standards19. +In 2026, 95.9% of homepages exhibited detectable Web Content Accessibility Guidelines (WCAG) 2 failures, an increase from 94.8% in 202519. The average homepage contained 56.1 distinct accessibility errors, representing a 10.1% year-over-year increase. This regression is largely driven by the ballooning complexity of modern DOM structures, which now average 1,437 elements per page (nearly double the complexity recorded in 2019\)19. +The digital accessibility landscape is dominated by six persistent failure modes, which account for 96% of all detected issues19. Table 3 details these widespread barriers. + +| Accessibility Failure | Prevalence (2026) | Impact on User Experience & Assistive Technology | +| :---- | :---- | :---- | +| **Low Contrast Text** | 83.9% of pages | Renders text unreadable for users with low vision, color blindness, or in high-glare environments. WCAG strictly requires a 4.5:1 ratio for standard text and 3:1 for large text19. | +| **Missing Alternative Text** | 53.1% of pages | Leaves screen reader users without access to image-based content. Crucially, when linked images lack alt text, it completely breaks structural navigation19. | +| **Missing Form Input Labels** | 51.0% of pages | Prevents users reliant on assistive technologies from understanding required form inputs (often relying on disappearing placeholders), blocking critical transactional pathways19. | +| **Empty Links** | 46.3% of pages | Anchor tags containing no descriptive text confuse assistive technologies regarding the destination of the hyperlink, destroying contextual navigation19. | +| **Empty Buttons** | 30.6% of pages | Buttons lacking programmatic purpose indicators render interactive elements entirely unusable for screen reader users19. | +| **Missing Document Language** | 13.5% of pages | Prevents assistive technologies from loading the correct phonetic profile for speech synthesis, resulting in unintelligible screen reader output19. | + +A notable paradox in the 2026 data is the widespread misuse of WAI-ARIA (Web Accessibility Initiative – Accessible Rich Internet Applications). ARIA usage increased by 27% in a single year, yet pages utilizing ARIA averaged 59.1 errors compared to 42 errors on pages without it19. This indicates that developers frequently implement complex ARIA attributes incorrectly, overriding native HTML semantics and actively degrading the experience for screen reader users rather than enhancing it. +Platform choice also influences baseline accessibility. WordPress-powered homepages averaged 52.8 errors, while heavily customized e-commerce platforms like Shopify and Magento averaged 75.1 and 75.8 errors, respectively19. The legal ramifications of these systemic failures are severe and escalating. In 2025, over 5,114 ADA Title III website accessibility lawsuits were filed in the United States, highlighting the untenable financial and reputational risk of deploying inaccessible digital products21. + +## **Evaluation Criteria of Premier Web Awards** + +Recognizing the pinnacle of web engineering and design requires rigorous evaluation matrices. Industry-leading awards utilize multidimensional rubrics to separate competent functionality from true digital excellence. Understanding these criteria is essential for engineers and designers aiming to build technologically impressive platforms that garner global recognition. +Winning these awards provides substantial compounding business benefits. An Awwwards Site of the Day (SOTD) generates immense organic traffic, establishes premium market positioning, justifies higher agency pricing models, and serves as a powerful talent recruitment vector for top-tier developers24. Table 4 contrasts the judging frameworks of the four major industry awards. + +| Award Platform | Judging System & Jury Composition | Core Evaluation Criteria | Focus & Target Audience | +| :---- | :---- | :---- | :---- | +| **Awwwards** | International expert jury; highest/lowest outlier scores are removed to ensure statistical fairness24. | Design (40%), Usability (30%), Creativity (20%), Content (10%)24. | The industry standard for digital agencies. Rewards holistic excellence balancing aesthetics and performance24. | +| **CSS Design Awards (CSSDA)** | 200+ international judges evaluating sites across Solo, Studio, and Agency size tiers24. | UI Design, UX Design, Innovation (technical execution of WebGL/WebGPU)24. | Heavily focused on UI craft and UX-led product sites. Features separate Judge and Public voting27. | +| **The FWA** | 500+ members from 35+ countries with strict gender balance. Features "Live Judging" visibility24. | Unconventional interaction design, emerging tech usage, boundary-pushing concepts24. | Rewards highly experimental, bold, and unconventional work. The oldest web award platform24. | +| **The Webby Awards** | Judged by the International Academy of Digital Arts and Sciences (IADAS)30. | Content, Structure/Navigation, Visual Design, Functionality, Interactivity, Innovation30. | Mainstream prestige. Evaluates broad digital work, large brand campaigns, and overall cross-platform execution32. | + +For a Computer Scientist or Engineer, targeting the *Innovation* category of the CSSDA or the FWA represents the ultimate challenge. These categories specifically evaluate the implementation of novel rendering pipelines, advanced physics simulations, and the utilization of emerging browser APIs to create experiences previously thought impossible on the web24. + +## **Advanced Computational Web Technologies: The Engineering Frontier** + +For computer scientists and graphics engineers, modern web browsers have transcended their origins as simple document viewers to become robust, high-performance, heterogeneous computing platforms. Pushing a browser to its absolute hardware limits—to win an Awwwards SOTD or demonstrate raw engineering prowess to peers—requires abandoning traditional DOM manipulation. Instead, engineers must embrace the convergence of GPU compute architectures and low-level bytecode execution. +The two pillars of this high-performance paradigm in 2026 are **WebGPU** (for massively parallel compute and graphics rendering) and **WebAssembly (WASM)** (for near-native sequential and structured parallel CPU execution). + +### **1\. The WebGPU Architecture: Leaving WebGL Behind** + +For over a decade, WebGL (based on the aging OpenGL ES standard) served as the foundation for browser 3D graphics. However, WebGL's architecture relies on a global state machine and single-threaded command submission. This results in severe CPU bottlenecks; updating a system of 100,000 particles in WebGL required the CPU to calculate positions sequentially and upload megabytes of data to the GPU every single frame, choking the PCIe bus and limiting performance33. Furthermore, doing general-purpose computation in WebGL required a clumsy hack: encoding data into pixel colors, rendering it to a texture, and reading it back synchronously via getPixelsData, which blocked the main thread35. +WebGPU, which reached broad stability across all major browsers by 2026, fundamentally redesigns this architecture by mapping directly to modern native graphics APIs (Vulkan, Metal, Direct3D 12\)34. It introduces an immutable pipeline state, asynchronous command encoders, and most critically, **Compute Shaders** written in the WebGPU Shading Language (WGSL)33. +Compute shaders allow developers to harness the thousands of Arithmetic Logic Units (ALUs) on a GPU for general-purpose parallel computing (GPGPU)35. By utilizing **Storage Buffers**, which can hold up to 128 MiB of read/write data (compared to WebGL's tiny 64 kiB Uniform Buffers), developers can maintain massive state arrays permanently on the GPU without ever transferring data back to the CPU39. +**WGSL Memory Alignment:** For systems engineers, mastering WGSL requires understanding strict GPU memory alignment rules to prevent silent data corruption. Graphics hardware demands that bytes are aligned by powers of 2\. In WGSL, the alignment of a struct is calculated as the maximum alignment of its members: AlignOf(S) \= max(AlignOfMember(S, M1), ... , AlignOfMember(S, Mn))40. A common trap involves the vec3 type, which has a size of 12 bytes but a required alignment of 16 bytes40. If an engineer creates an array of vec3 positions in a Storage Buffer, the GPU will expect 4 bytes of padding after every vector. Failing to account for this padding when writing Float32Array data from JavaScript will result in staggered, misaligned data corruption across the entire buffer40. +**The Compute-to-Render Pipeline:** The most powerful pattern in WebGPU is the seamless transition from compute to render. A compute shader calculates complex physics and writes the results to a shared Storage Buffer. Because this buffer is declared with both GPUBufferUsage.STORAGE and GPUBufferUsage.VERTEX flags, the subsequent render pass reads the exact same buffer in GPU memory42. By utilizing *indirect drawing*—where the compute shader determines the exact number of active instances and writes that count to an indirectBuffer—the CPU is entirely removed from the rendering loop. The GPU dispatches compute, evaluates the result, and renders millions of dynamic vertices in under 2 milliseconds, entirely autonomously42. + +### **2\. Engineering Masterpiece: Real-Time Fluid Dynamics** + +To showcase elite engineering prowess, implementing a real-time fluid dynamics simulation using WebGPU is highly effective. A GPU-powered Navier-Stokes fluid solver creates visually mesmerizing, mathematically rigorous interactions where vivid colors swirl and advect in response to cursor movement, running flawlessly at 60 FPS even on mobile devices43. +The simulation relies on the Eulerian grid-based approach, solving the incompressible Navier-Stokes equations directly in WGSL compute shaders43. The algorithm splits the differential equations into multiple discrete stages, each handled by a dedicated compute shader dispatch: + +> 1. **Advection:** Moving the velocity vector field and a dye (color) field through themselves over time. The compute shader looks backward along the velocity vector to find the previous state and interpolates the new value43. +> 2. **Divergence:** Calculating the expansion or contraction of the fluid velocity. For the fluid to remain incompressible, the divergence must eventually be zero43. +> 3. **Jacobi Iteration (Pressure Solving):** Because pressure is a global phenomenon affecting the entire grid simultaneously, the Poisson equation for pressure cannot be solved in a single pass. Compute shaders ping-pong data between two double-buffered Storage Buffers (reading from buffer A, writing to buffer B, then swapping) for 20 to 50 iterations per frame to iteratively resolve the pressure gradient43. +> 4. **Gradient Subtraction:** Subtracting the final computed pressure gradient from the velocity field to ensure the resulting velocity field is completely divergence-free43. + +By fine-tuning parameters like vorticity confinement (which restores microscopic energy lost to numerical dissipation in the grid) and integrating post-processing bloom effects, engineers create a highly interactive simulation that highlights a profound mastery of grid-based physics and parallel compute43. + +### **3\. WebAssembly (WASM) and the CPU/GPU Boundary** + +While WebGPU handles massive data parallelism effortlessly, complex game logic, physics collision trees (like Bounding Volume Hierarchies), and AI decision-making rely on heavy branching logic that GPUs handle poorly. WebAssembly bridges this gap, providing a stack-based virtual machine that compiles languages like C++, Rust, and Go into a compact, low-level binary format that executes at roughly 80-95% of native CPU speed47. +Recent advancements have dramatically increased WASM's computational capabilities: + +* **SIMD 128-bit Vectorization:** Single Instruction, Multiple Data (SIMD) allows WASM to process 16 bytes of data in a single CPU cycle using 128-bit vector registers. For tasks like lexical analysis, parsing, or physics collision detection, WASM SIMD provides up to a 16x mathematical speedup over sequential JavaScript execution50. For instance, a SIMD-enabled lexer can load 16 characters into a v128 register and check them all for a newline character in a single hardware instruction51. +* **WasmGC (Garbage Collection):** Finalized across major browsers in late 2024, WasmGC allows high-level languages (like Java or Kotlin) to utilize the browser's native garbage collector, drastically reducing binary sizes and startup times by eliminating the need to ship a custom garbage collector within the WASM module52. +* **Multithreading with SharedArrayBuffer:** True multi-threading in the browser allows WASM to utilize a pool of Web Workers. By utilizing SharedArrayBuffer and Atomics, engineers can implement lock-free ring buffers and task parallelism that rival native pthreads53. + +The optimal high-performance architecture in 2026 utilizes a "double-brain" approach: Rust/WASM handles spatial partitioning (Frustum culling), complex glTF asset parsing, and zero-copy data staging, while WebGPU ingests this highly organized data for compute-heavy simulation and rasterization55. + +### **4\. The Absolute Frontier: 3D Gaussian Splatting and In-Browser LLMs** + +Beyond fluid dynamics, two cutting-edge technologies are currently defining the extreme frontier of browser-based engineering: +**3D Gaussian Splatting (3DGS):** 3DGS has revolutionized novel view synthesis, bypassing expensive volumetric ray marching in favor of an explicit point-based representation56. Implementing this in the browser (e.g., frameworks like WebSplatter) requires overcoming the lack of global hardware atomics in WebGPU58. Engineers utilize a tile-based differentiable rasterizer where millions of anisotropic 3D Gaussians are projected onto the 2D screen56. To render them correctly with front-to-back alpha blending, the Gaussians must be sorted by depth every single frame. Because CPU sorting is impossibly slow for millions of points, engineers implement highly optimized, wait-free Radix Sorts entirely in WebGPU compute shaders. This leverages *subgroup operations* (warp shuffles), allowing threads within a workgroup to share data directly through GPU registers, bypassing slow shared memory access to achieve blistering sort speeds58. +**Local Large Language Models (LLMs):** Running AI inference locally in the browser ensures absolute privacy and zero server compute costs. Frameworks like WebLLM utilize WebGPU to run billion-parameter models (like Qwen2.5-1.5B) directly in the client61. However, transformer inference requires hundreds of small, sequential matrix multiplications per token. WebGPU's security model enforces strict validation on every operation to prevent memory out-of-bounds access. This validation creates a massive "dispatch overhead" of approximately 95 microseconds per command63. If left unoptimized, a naive port of an LLM will stall entirely on CPU-side API overhead. To achieve 20-40 tokens per second, engineers must aggressively employ *kernel fusion*—combining multiple operations (e.g., Matrix Multiply \+ SiLU activation \+ Add) into a single, massive WGSL compute shader. This minimizes the number of times the CPU has to dispatch commands to the GPU, proving that deep architectural optimization is the only path to achieving native-level AI performance in the browser63. + +## **Conclusion** + +The architecture of digital excellence in 2026 demands an uncompromising fusion of disciplines. Understanding user preferences requires acknowledging that aesthetics and ergonomics are inextricably linked; minimalist layouts, precise color psychology, and strict adherence to cognitive laws (Hick-Hyman, Fitts's) directly govern user retention, satisfaction, and conversion. Furthermore, the unforgiving performance metrics of Core Web Vitals and the systemic failures highlighted by the WebAIM 2026 report dictate that low latency and high accessibility must be foundational constraints, not post-launch afterthoughts. +For the modern computer scientist and engineer, the convergence of WebAssembly and WebGPU offers an unprecedented canvas. By mastering GPU memory layouts, compute-to-render pipelines, and advanced algorithms like Navier-Stokes fluid solvers or 3D Gaussian Splatting, engineers can bypass historical browser limitations entirely. Crafting a digital experience that wins global awards requires moving beyond traditional DOM manipulation and harnessing the raw, parallelized power of the GPU and SIMD-enabled CPU. Ultimately, this mastery allows engineers to deliver immersive, technologically profound environments that captivate users and redefine the boundaries of the open web. + +#### **Works cited** + +> 1. Website Design Statistics 2026: 99+ Stats & Insights \[Expert Analysis\] \- Marketing LTB, [https://marketingltb.com/blog/statistics/website-design-statistics/](https://marketingltb.com/blog/statistics/website-design-statistics/) +> 2. Website Design Statistics | Fact-Checked 2026 \- Gitnux, [https://gitnux.org/website-design-statistics/](https://gitnux.org/website-design-statistics/) +> 3. 14 Web Design Trends to Keep up with in 2026 \- UX Pilot, [https://uxpilot.ai/blogs/web-design-trends-2026](https://uxpilot.ai/blogs/web-design-trends-2026) +> 4. How Many People Use "Dark Mode" to Reduce Eye Strain? \- Lens.com, [https://www.lens.com/questions-answered/dark-mode-usage-eye-strain-reduction-stats/](https://www.lens.com/questions-answered/dark-mode-usage-eye-strain-reduction-stats/) +> 5. Theories and Models for Human-Computer Interaction \- Laboratoire de Recherche en Informatique, [https://www.lri.fr/\~mbl/ENS/FONDIHM/2022/slides/FundHCI2-7-Theories.pdf](https://www.lri.fr/~mbl/ENS/FONDIHM/2022/slides/FundHCI2-7-Theories.pdf) +> 6. Finger-Based Pointing Performance on Mobile Touchscreen Devices: Fitts' Law Fits, [https://www.researchgate.net/publication/281065644\_Finger-Based\_Pointing\_Performance\_on\_Mobile\_Touchscreen\_Devices\_Fitts'\_Law\_Fits](https://www.researchgate.net/publication/281065644_Finger-Based_Pointing_Performance_on_Mobile_Touchscreen_Devices_Fitts'_Law_Fits) +> 7. Evaluation of Child–Computer Interaction Using Fitts' Law: A Comparison between a Standard Computer Mouse and a Head Mouse \- PMC, [https://pmc.ncbi.nlm.nih.gov/articles/PMC8197934/](https://pmc.ncbi.nlm.nih.gov/articles/PMC8197934/) +> 8. Core Web Vitals 2026: Fix Speed or Keep Losing Traffic \- Idea Fueled, [https://ideafueled.com/blog/core-web-vitals-2026-explained/](https://ideafueled.com/blog/core-web-vitals-2026-explained/) +> 9. Introducing INP to Core Web Vitals | Google Search Central Blog, [https://developers.google.com/search/blog/2023/05/introducing-inp](https://developers.google.com/search/blog/2023/05/introducing-inp) +> 10. Interaction to Next Paint (INP): What It Is, How to Measure and Improve It \- Core Web Vitals & PageSpeed Consultant | Arjen Karel, [https://www.corewebvitals.io/core-web-vitals/interaction-to-next-paint](https://www.corewebvitals.io/core-web-vitals/interaction-to-next-paint) +> 11. Interaction to Next Paint (INP) \- web.dev, [https://web.dev/articles/inp](https://web.dev/articles/inp) +> 12. Core Web Vitals Pass Rate 2026: 49.1% of 13.75M Sites, [https://www.pagespeedmatters.com/resources/data-studies/core-web-vitals-pass-rates](https://www.pagespeedmatters.com/resources/data-studies/core-web-vitals-pass-rates) +> 13. The Thresholds of Perception in UX \- DEV Community, [https://dev.to/deanius/the-thresholds-of-perception-in-ux-435g](https://dev.to/deanius/the-thresholds-of-perception-in-ux-435g) +> 14. Executing UX Animations: Duration and Motion Characteristics \- NN/G, [https://www.nngroup.com/articles/animation-duration/](https://www.nngroup.com/articles/animation-duration/) +> 15. The ultimate guide to proper use of animation in UX | Break Free Design Blog, [https://www.breakfreegraphics.com/design-blog/the-ultimate-guide-to-proper-use-of-animation-in-ux/](https://www.breakfreegraphics.com/design-blog/the-ultimate-guide-to-proper-use-of-animation-in-ux/) +> 16. Real-Time Micro-Interaction Tweaks Based on First-Time User Cues: Precision Optimization to Slash Onboarding Drop-Off, [https://arlozorov.tik-tak.net/2025/02/17/real-time-micro-interaction-tweaks-based-on-first-time-user-cues-precision-optimization-to-slash-onboarding-drop-off/](https://arlozorov.tik-tak.net/2025/02/17/real-time-micro-interaction-tweaks-based-on-first-time-user-cues-precision-optimization-to-slash-onboarding-drop-off/) +> 17. Micro-Interactions That Don't Annoy: The 3-Second Rule for UI Animation \- Medium, [https://robertcelt95.medium.com/micro-interactions-that-dont-annoy-the-3-second-rule-for-ui-animation-9881300cd187](https://robertcelt95.medium.com/micro-interactions-that-dont-annoy-the-3-second-rule-for-ui-animation-9881300cd187) +> 18. Web Accessibility Statistics (2026): Research & Data from AudioEye, [https://www.audioeye.com/post/accessibility-statistics/](https://www.audioeye.com/post/accessibility-statistics/) +> 19. Web Accessibility Is Getting Worse According to the 2026 WebAIM Million Report, [https://prioritypixels.co.uk/insights/web-accessibility-is-getting-worse-according-to-the-2026-webaim-million-report/](https://prioritypixels.co.uk/insights/web-accessibility-is-getting-worse-according-to-the-2026-webaim-million-report/) +> 20. Web Accessibility Statistics 2026: WCAG and Lawsuit Data, [https://www.digitalapplied.com/blog/web-accessibility-statistics-2026-wcag-lawsuit-data](https://www.digitalapplied.com/blog/web-accessibility-statistics-2026-wcag-lawsuit-data) +> 21. 2026 Web Accessibility Statistics: Compliance, Lawsuits & Trends \- Be Accessible, [https://beaccessible.com/post/web-accessibility-statistics/](https://beaccessible.com/post/web-accessibility-statistics/) +> 22. 6 Common Web Accessibility Issues \+ Fixes (2026) \- WebYes, [https://www.webyes.com/blogs/common-web-accessibility-issues/](https://www.webyes.com/blogs/common-web-accessibility-issues/) +> 23. 5 Essential Dos and Don'ts for Accessible Web Development \- Major Tom, [https://www.majortom.com/mercury-blog/5-essential-dos-and-donts-for-accessible-web-development](https://www.majortom.com/mercury-blog/5-essential-dos-and-donts-for-accessible-web-development) +> 24. Award-Winning Web Design: Judging Criteria Decoded | Utsubo, [https://www.utsubo.com/blog/award-winning-website-design-guide](https://www.utsubo.com/blog/award-winning-website-design-guide) +> 25. Evaluation System \- Awwwards, [https://www.awwwards.com/about-evaluation/](https://www.awwwards.com/about-evaluation/) +> 26. CSS Design Awards \- Website Awards \- Best Web Design Inspiration \- CSS Awards, [https://www.cssdesignawards.com/](https://www.cssdesignawards.com/) +> 27. CSS Design Awards \- Design Directory, [https://www.designdirectory.io/resources/awards/cssda](https://www.designdirectory.io/resources/awards/cssda) +> 28. Studio Rubric \- CSS Design Awards, [https://www.cssdesignawards.com/sites/studio-rubric/39034/](https://www.cssdesignawards.com/sites/studio-rubric/39034/) +> 29. Favourite Website Awards (FWAs) website | Communication Arts, [https://www.commarts.com/project/10926/favourite-website-awards-fwas-website](https://www.commarts.com/project/10926/favourite-website-awards-fwas-website) +> 30. Judging Criteria \- The Webby Awards, [https://www.webbyawards.com/judging-criteria/](https://www.webbyawards.com/judging-criteria/) +> 31. Webby Awards Process, [https://www.webbyawards.com/awarding/](https://www.webbyawards.com/awarding/) +> 32. The Webby Awards Review: Criteria, Pricing, and Categories (2026) \- Web Design Awards, [https://www.webdesignawards.io/awards/webby-awards](https://www.webdesignawards.io/awards/webby-awards) +> 33. From WebGL to WebGPU | Chrome for Developers, [https://developer.chrome.com/docs/web-platform/webgpu/from-webgl-to-webgpu](https://developer.chrome.com/docs/web-platform/webgpu/from-webgl-to-webgpu) +> 34. WebGL vs. WebGPU Explained \- Three.js Roadmap, [https://threejsroadmap.com/blog/webgl-vs-webgpu-explained](https://threejsroadmap.com/blog/webgl-vs-webgpu-explained) +> 35. WebGPU computations performance in comparison to WebGL \- Pixels Commander, [https://pixelscommander.com/2021/11/30/webgpu-computations-performance-in-comparison-to-webgl/](https://pixelscommander.com/2021/11/30/webgpu-computations-performance-in-comparison-to-webgl/) +> 36. What Browsers Do in the Shaders: A Measurement Study of WebGPU Privacy \- arXiv, [https://arxiv.org/pdf/2606.26412](https://arxiv.org/pdf/2606.26412) +> 37. WebGPU Shading Language \- W3C, [https://www.w3.org/TR/WGSL/](https://www.w3.org/TR/WGSL/) +> 38. The Pipeline | Learn Wgpu, [https://sotrh.github.io/learn-wgpu/beginner/tutorial3-pipeline/](https://sotrh.github.io/learn-wgpu/beginner/tutorial3-pipeline/) +> 39. WebGPU Storage Buffers, [https://webgpufundamentals.org/webgpu/lessons/webgpu-storage-buffers.html](https://webgpufundamentals.org/webgpu/lessons/webgpu-storage-buffers.html) +> 40. Memory Layout in WGSL | Learn Wgpu \- GitHub Pages, [https://sotrh.github.io/learn-wgpu/showcase/alignment/](https://sotrh.github.io/learn-wgpu/showcase/alignment/) +> 41. Buffers and GPU memory alignment : r/opengl \- Reddit, [https://www.reddit.com/r/opengl/comments/10wv164/buffers\_and\_gpu\_memory\_alignment/](https://www.reddit.com/r/opengl/comments/10wv164/buffers_and_gpu_memory_alignment/) +> 42. Compute to Render \- WebGPU \- Teach Me, [https://www.teachme.sh/webgpu/compute-to-render](https://www.teachme.sh/webgpu/compute-to-render) +> 43. WebGL Fluid Simulation by Pavel Dobryakov, [https://www.webgpu.com/showcase/webgl-fluid-simulation-by-pavel-dobryakov/](https://www.webgpu.com/showcase/webgl-fluid-simulation-by-pavel-dobryakov/) +> 44. Interactive Distributed Fluid Simulation on the GPU \- BME, [http://cg.iit.bme.hu/\~szirmay/fluid.pdf](http://cg.iit.bme.hu/~szirmay/fluid.pdf) +> 45. Running Fluid Simulations in WebGL I \- Simple Convection | Jamie J Quinn, [https://jamiejquinn.com/running-fluid-simulations-in-webgl-i-simple-convection/](https://jamiejquinn.com/running-fluid-simulations-in-webgl-i-simple-convection/) +> 46. wgsl: document how much of an alignment request is checked by the API \#2999 \- GitHub, [https://github.com/gpuweb/gpuweb/issues/2999](https://github.com/gpuweb/gpuweb/issues/2999) +> 47. Web Assembly: Performance gains and use cases for modern browsers \- ResearchGate, [https://www.researchgate.net/publication/395576400\_Web\_Assembly\_Performance\_gains\_and\_use\_cases\_for\_modern\_browsers](https://www.researchgate.net/publication/395576400_Web_Assembly_Performance_gains_and_use_cases_for_modern_browsers) +> 48. An Overview of WebAssembly for IoT: Background, Tools, State-of-the-Art, Challenges, and Future Directions \- MDPI, [https://www.mdpi.com/1999-5903/15/8/275](https://www.mdpi.com/1999-5903/15/8/275) +> 49. WebGPU vs WebAssembly \- Metavert, [https://www.metavert.io/compare/webgpu-vs-wasm](https://www.metavert.io/compare/webgpu-vs-wasm) +> 50. WebAssembly and SIMD \- Wasmer, [https://blog.wasmer.io/webassembly-and-simd-13badb9bf1a8](https://blog.wasmer.io/webassembly-and-simd-13badb9bf1a8) +> 51. 128-bit Vector Processing in the Browser via WASM-SIMD \- NitroIDE, [https://nitroide.com/blog/wasm-simd-128-bit-vector-processing-browser.html](https://nitroide.com/blog/wasm-simd-128-bit-vector-processing-browser.html) +> 52. Rust \+ WebAssembly 2025: Why WasmGC and SIMD Change Everything \- DEV Community, [https://dev.to/dataformathub/rust-webassembly-2025-why-wasmgc-and-simd-change-everything-3ldh](https://dev.to/dataformathub/rust-webassembly-2025-why-wasmgc-and-simd-change-everything-3ldh) +> 53. WebAssembly/shared-everything-threads \- proposals \- GitHub, [https://github.com/WebAssembly/shared-everything-threads/blob/main/proposals/shared-everything-threads/Overview.md](https://github.com/WebAssembly/shared-everything-threads/blob/main/proposals/shared-everything-threads/Overview.md) +> 54. JavaScript Atomics and SharedArrayBuffer in 2026: Practical Patterns for Cross-Worker State | jsmanifest, [https://jsmanifest.com/atomics-sharedarraybuffer-cross-worker-state](https://jsmanifest.com/atomics-sharedarraybuffer-cross-worker-state) +> 55. WebGPU × WebAssembly rendering and computing engine for scientific workloads in the browser : r/rust \- Reddit, [https://www.reddit.com/r/rust/comments/1t1pprc/webgpu\_webassembly\_rendering\_and\_computing\_engine/](https://www.reddit.com/r/rust/comments/1t1pprc/webgpu_webassembly_rendering_and_computing_engine/) +> 56. 3D Gaussian Splatting Rasterization: A Survey \- Preprints.org, [https://www.preprints.org/manuscript/202607.0776](https://www.preprints.org/manuscript/202607.0776) +> 57. 3D Gaussian Splatting Rasterization: A Survey \- Preprints.org, [https://www.preprints.org/frontend/manuscript/ae490aa5d6326f85aaf271c2b2c023ee/download\_pub](https://www.preprints.org/frontend/manuscript/ae490aa5d6326f85aaf271c2b2c023ee/download_pub) +> 58. WebSplatter: Enabling Cross-Device Efficient Gaussian Splatting in Web Browsers via WebGPU \- arXiv, [https://arxiv.org/html/2602.03207v1](https://arxiv.org/html/2602.03207v1) +> 59. Considerations for subgroups · Issue \#3950 \- GitHub, [https://github.com/gpuweb/gpuweb/issues/3950](https://github.com/gpuweb/gpuweb/issues/3950) +> 60. What's New in WebGPU (Chrome 134\) | Blog, [https://developer.chrome.com/blog/new-in-webgpu-134](https://developer.chrome.com/blog/new-in-webgpu-134) +> 61. WebLLM: A High-Performance In-Browser LLM Inference Engine \- arXiv, [https://arxiv.org/html/2412.15803v2](https://arxiv.org/html/2412.15803v2) +> 62. Characterizing WebGPU Dispatch Overhead for LLM Inference Across Four GPU Vendors, Three Backends, and Three Browsers \- arXiv, [https://arxiv.org/pdf/2604.02344](https://arxiv.org/pdf/2604.02344) +> 63. \[2604.02344\] Characterizing WebGPU Dispatch Overhead for LLM Inference Across Four GPU Vendors, Three Backends, and Three Browsers \- arXiv, [https://arxiv.org/abs/2604.02344](https://arxiv.org/abs/2604.02344) +> 64. Characterizing WebGPU Dispatch Overhead for LLM Inference Across Four GPU Vendors, Three Backends, and Three Browsers \- arXiv, [https://arxiv.org/html/2604.02344v1](https://arxiv.org/html/2604.02344v1) + +[image1]: + +[image2]: + +[image3]: + +[image4]: + +[image5]: \ No newline at end of file diff --git a/docs/moon/research/Global Interactive Portfolio Website Architecture.md b/docs/moon/research/Global Interactive Portfolio Website Architecture.md new file mode 100644 index 0000000..fe33432 --- /dev/null +++ b/docs/moon/research/Global Interactive Portfolio Website Architecture.md @@ -0,0 +1,190 @@ +# **Architecting the Immersive Web: A Global Synthesis of 3D Design, Artificial Intelligence, and Mathematical Optimization** + +The digital portfolio has evolved far beyond a static repository of hyperlinks and textual case studies. In the contemporary web landscape, particularly among globally recognized award-winning agencies like Merci Michel, Active Theory, Studio Freight, and Obys, the portfolio is treated as a highly interactive, computational canvas1. These platforms serve as living demonstrations of an agency's or individual's engineering prowess, seamlessly blending high-fidelity 3D rendering, sophisticated motion kinematics, data visualization, and, increasingly, artificial intelligence and mathematical optimization algorithms2. Designing and deploying a visually stunning personal or project information website requires mastering a complex convergence of technologies. It necessitates moving away from standard Document Object Model (DOM) manipulations and embracing Graphics Processing Unit (GPU) acceleration, multithreaded browser environments, and binary execution formats. +The architectural paradigms deployed by industry-leading web studios reveal a definitive shift toward immersive, state-driven applications that blur the boundaries between native software and browser-based experiences. Engineers are building bespoke rendering pipelines, compiling C++ optimization solvers into WebAssembly, and executing machine learning models directly on the client's local hardware to eliminate latency4. The following report provides an exhaustive, expert-level analysis of the technology stacks, rendering architectures, interaction mechanics, and computational integrations required to build a world-class, multi-layered interactive web portfolio. + +## **The Foundation: Meta-Frameworks and Hydration Strategies** + +The foundational architecture of a highly interactive web application is defined by its routing mechanism and rendering framework. A fundamental tension exists in modern web development: shipping heavy JavaScript bundles enables seamless client-side routing and persistent application states, but it severely degrades initial loading performance and Core Web Vitals7. Award-winning sites navigate this tension by selecting a meta-framework that aligns precisely with the project's interactivity ratio. Globally, the industry has largely converged on two distinct architectural philosophies, represented by Next.js and Astro. +Next.js, maintained by Vercel, treats the entire website as a cohesive, stateful application7. Utilizing the App Router and React Server Components (RSC), Next.js executes server-side logic while maintaining a persistent client-side React runtime7. This persistence is absolutely vital for applications requiring uninterrupted background states, such as a continuous WebGL canvas, persistent audio playback, or global state stores that must survive page navigations11. However, the inclusion of the React runtime and the subsequent hydration process—where the browser attaches event listeners to the server-rendered HTML—introduces a significant JavaScript payload8. For highly complex 3D applications, this payload can compete for the main thread's resources, potentially impacting the Interaction to Next Paint (INP) metric. +Conversely, Astro employs a fundamentally different paradigm known as "Islands Architecture"7. Astro renders pages to pure static HTML by default and explicitly prohibits client-side JavaScript unless explicitly requested via hydration directives, such as client:load or client:visible7. This approach yields profound performance benefits. Empirical data indicates that an Astro-built page might ship approximately 9 KB of JavaScript, compared to a 463 KB payload for an equivalent Next.js page—a nearly 50-fold reduction9. For a portfolio heavily reliant on initial load speed and search engine optimization, Astro ensures that the main thread remains idle, preventing layout shifts and guaranteeing immediate responsiveness9. Furthermore, Astro is framework-agnostic, allowing developers to mount isolated React, Vue, Svelte, or vanilla Three.js components within the same document12. + +| Metric | Next.js (App Router) | Astro (Islands Architecture) | +| :---- | :---- | :---- | +| **Default Rendering** | React Server Components & Client Hydration | Zero-JavaScript Static HTML | +| **JavaScript Payload** | High (Includes React Runtime & App State) | Minimal (Component-specific only) | +| **State Persistence** | Global persistence across route changes | Ephemeral per page (requires View Transitions) | +| **Framework Support** | Strictly React ecosystem | Agnostic (React, Vue, Svelte, Vanilla JS) | +| **Optimal Use Case** | Highly dynamic, continuous 3D application shells | Content-heavy, SEO-driven, modular interactive sections | + +The selection of the meta-framework dictates the overarching routing strategy for the WebGL canvas. If Astro is selected, the 3D scene must be designed as an isolated island that either gracefully initializes and tears down between page loads, or relies heavily on the View Transitions API to maintain a semblance of continuity13. If Next.js is selected, the architecture must support a global, persistent canvas that spans the entire application lifecycle. + +## **Architecting the Persistent Canvas and Modularity** + +To manage the immense complexity of a continuous 3D scene across multiple web routes, developers must adopt a highly modular, decoupled architecture. React Three Fiber (R3F), a custom React reconciler for Three.js, is the industry standard for declarative 3D graphics in modern web development14. R3F allows developers to construct 3D scenes using reusable, self-contained components, effectively translating the imperative, hierarchical nature of the Three.js scene graph into a composable React component tree. +When utilizing a persistent canvas in a Next.js environment, the WebGL context must reside outside the specific route components, typically instantiated at the root layout level15. A critical error frequently encountered in single-page applications heavily utilizing WebGL is the THREE.WebGLRenderer: Context Lost warning16. This occurs when the browser's garbage collector forcefully discards graphics assets to prevent memory leaks due to an abundance of instantiated WebGL contexts. Browsers impose a strict limit on active WebGL contexts per tab, usually capping between eight and sixteen16. To circumvent this, developers must strictly adhere to a singleton pattern for the renderer. When a user navigates between routes, the application must never unmount and remount the primary \ component18. Instead, it should clear the specific objects within the scene graph—calling dispose() on geometries, materials, and textures to free Video RAM (VRAM)—and mount new components within the existing, persistent canvas17. +Managing navigation within this persistent canvas requires specialized routing. Because traditional DOM-based routers trigger full component unmounts, developers embed lightweight, hook-based routers like Wouter directly inside the R3F \19. This allows the application to swap 3D models, alter lighting parameters, or transition camera positions based on the current URL without destroying the underlying WebGL context17. +Furthermore, synchronizing state across the standard HTML DOM and the WebGL canvas introduces significant performance challenges. Because the canvas and the DOM exist in separate rendering contexts, passing state via React's native Context API can trigger cascading, performance-degrading re-renders across both environments21. The optimal architectural solution is the integration of Zustand, a minimal, fast, and scalable bare-bones state management solution based on Flux principles21. Zustand allows developers to subscribe to state changes outside the standard React render cycle. A 3D component can read a Zustand store continuously within the useFrame animation loop without ever triggering a React reconciliation. This ensures that DOM-based UI elements—such as HTML sliders, buttons, or scroll triggers—can manipulate the 3D scene with zero latency and zero unnecessary repaints. + +## **Multithreading the Graphics Pipeline: OffscreenCanvas and Web Workers** + +Pushing millions of pixels and executing continuous mathematical operations in a browser inherently threatens the main execution thread. The JavaScript runtime operates on a single-threaded event loop23. If the main thread is occupied parsing a heavy JSON payload, executing a React reconciliation, or calculating layout shifts, the requestAnimationFrame loop responsible for rendering the 3D canvas is blocked, resulting in dropped frames and visual jank23. In an immersive portfolio, any input latency or visual stutter instantly destroys the illusion of a premium experience. +The architectural solution to this bottleneck is the implementation of the OffscreenCanvas API paired with Web Workers23. By calling the transferControlToOffscreen() method on the primary HTML canvas element, the application successfully passes ownership of the graphics context to a dedicated, background Web Worker thread23. Following this transfer, the entire Three.js or R3F runtime is initialized and executed exclusively inside the Web Worker26. +This paradigm shift yields extraordinary performance benefits. Because the rendering pipeline is decoupled from the DOM, heavy UI manipulations on the main thread have absolutely zero impact on the rendering loop23. The graphics engine can maintain a flawless 60 or 120 frames per second, regardless of how severely the main UI thread is choked by other processes. Communication between the main thread and the worker is conducted solely via asynchronous postMessage events24. The main thread serializes user inputs—such as pointer coordinates, scroll positions, or click events—and sends them to the worker, which then updates the camera or scene logic accordingly. While this introduces a microsecond communication overhead, the guarantee of an unblocked rendering loop makes it a mandatory architecture for highly complex award-winning sites. + +| Execution Environment | Rendering Target | Thread Status | Blocked by DOM Events? | Ideal Use Case | +| :---- | :---- | :---- | :---- | :---- | +| **Main Thread (Default)** | Standard \ | Shared with UI | Yes | Simple visualizations, static 3D models | +| **Web Worker** | OffscreenCanvas | Dedicated Graphics | No | High-particle simulations, complex shaders, persistent scenes | + +## **Advanced Graphics: From WebGL to WebGPU and TSL** + +While Three.js elegantly abstracts the verbose and highly complex WebGL API into accessible JavaScript objects, the frontier of web graphics has definitively shifted toward WebGPU1. WebGPU is a modern web standard designed to provide low-level, high-performance access to modern hardware architectures, aligning closely with native APIs such as Vulkan, Metal, and Direct3D 1228. +Unlike WebGL, which is fundamentally designed around a traditional rendering pipeline, WebGPU introduces compute shaders as first-class citizens29. This enables highly parallel, general-purpose computations directly on the graphics card29. Pioneering agencies have historically built custom 3D engines—such as Active Theory's "Hydra" engine—to bypass the overhead of browser compositing and security checks, maximizing graphics throughput4. While building a proprietary C++ or Rust engine compiled to WebAssembly represents the absolute apex of web engineering, modern libraries now democratize this power. The transition to WebGPU allows portfolios to simulate millions of fluid particles, run complex physics engines, or execute generative algorithms at stable frame rates by offloading calculations from the CPU directly to GPU compute shaders2. + +### **The Evolution of Post-Processing** + +Award-winning sites frequently utilize post-processing to apply cinematic visual effects, such as bloom, depth of field, chromatic aberration, and fluid distortions. Historically, developers utilized the EffectComposer in Three.js, which chained multiple full-screen render passes together32. This linear conveyor-belt model resulted in massive GPU overhead, as the entire screen was redrawn for each individual effect, forcing the cost to scale linearly with the length of the chain32. Furthermore, EffectComposer had no native mechanism to share depth or normal data across passes, requiring the scene to be re-rendered from scratch for effects like Screen-Space Reflections or Ambient Occlusion32. +The modern approach abandons EffectComposer in favor of a node-based architecture. Currently, this is achieved either via highly optimized libraries like pmndrs/postprocessing, which intelligently merge multiple fragment shaders into a single custom material pass, or natively via the new Three.js RenderPipeline32. The RenderPipeline relies on the Three.js Shading Language (TSL), which allows developers to define GPU operations natively in JavaScript rather than writing raw GLSL string blocks32. By expressing post-processing chains as graphical nodes, the compiler can analyze the entire graph, optimize branching, share data sources, and generate highly optimized WebGPU or WebGL2 code automatically, vastly reducing draw calls32. + +### **Fluid Distortion and Optical Illusions** + +A particularly popular aesthetic in contemporary web design involves fluid, liquid, or water-like distortion transitions affecting the entire page34. Rather than employing mathematically expensive fluid dynamics simulations, developers achieve this via optical illusions using off-screen rendering35. +The mechanism involves capturing mouse movements to draw radial gradients (representing ripples) onto a hidden 2D canvas35. As these ripples age, their opacity naturally decays35. This hidden 2D canvas is then converted into a dynamic texture and passed as a uniform variable into a post-processing fragment shader35. The shader utilizes the brightness values of the ripple texture to offset the UV coordinates of the underlying 3D scene35. By perturbing the UV coordinates based on the generated normal map of the ripples, the shader creates the convincing optical illusion of a refractive liquid lens distorting the web page, requiring only a fraction of the computational power of a true physics simulation34. + +## **Audio Reactivity and Immersive Spatial Environments** + +To transcend purely visual design and engage multiple senses, cutting-edge portfolios integrate real-time audio reactivity. The architecture for synchronizing WebGL rendering with audio playback relies heavily on the Web Audio API36. +The implementation begins by instantiating an AudioContext and routing an audio source through an AnalyserNode37. The AnalyserNode is tasked with performing a Fast Fourier Transform (FFT) on the audio signal, returning real-time frequency data arrays representing the amplitude of bass, mid, and treble frequencies37. This numerical data is sampled on every frame within the requestAnimationFrame loop and passed directly to the custom GLSL shader materials via uniform variables. +Within the Vertex Shader, three-dimensional Simplex Noise functions can be modulated by this audio frequency uniform to displace the geometry37. For instance, when a low-frequency bass drum impacts, the noise multiplier increases, causing the vertices to expand violently outward along their calculated normals, resulting in a pulsing, spiky geometry that reacts synchronously to the beat37. Simultaneously, the Fragment Shader can calculate a dynamic Fresnel effect37. By calculating the dot product of the camera's view direction and the vertex normal, the shader identifies the grazing edges of the geometry. This value is inverted and exponentiated to create a luminous, glowing edge that intensifies in direct correlation with the audio's volume37. + +### **360-Degree Panoramic Navigation** + +For portfolios showcasing architectural visualizations, large-scale environmental art, or nature photography (such as the Avoriaz ski resort concept), immersive 360-degree panoramic designs offer deep user engagement38. In Three.js, this is achieved using equirectangular mapping38. An equirectangular image—a specific projection that maps the latitude and longitude of a sphere onto a flat, 2:1 ratio grid—is loaded as a Texture and applied to the inner surface of a large, high-segment SphereGeometry38. +The virtual camera is placed precisely at the origin coordinates (0, 0, 0\) inside this sphere. By implementing OrbitControls or advanced CameraControls libraries, developers constrain the camera's translation while allowing polar and azimuthal rotation, enabling the user to look around the environment smoothly39. Advanced implementations take this further by utilizing Raycasters mapped to the camera's exact orientation to detect intersections with invisible 3D coordinates. When the Raycaster intersects a designated coordinate, the application utilizes a CSS2DRenderer to project HTML overlays—revealing data points, project details, or navigation links—directly onto the 3D space, merging spatial navigation with accessible, indexable DOM text. + +## **Motion Kinematics and View Transitions** + +The connective tissue of a premium, award-winning portfolio is its motion design. Sites of this caliber actively eschew native browser scrolling and abrupt layout changes, favoring continuous, inertia-driven physics models. +Smooth scrolling is typically implemented using libraries like Lenis, which normalizes the scroll wheel, trackpad, and touch inputs across different operating systems40. Lenis applies a mathematical dampening function to the scroll delta, creating a buttery, momentum-based scrolling experience40. This continuously interpolated scroll value is then piped directly into animation libraries like the GreenSock Animation Platform (GSAP) via the ScrollTrigger plugin3. GSAP allows developers to scrub animation timelines based strictly on the user's scroll position. This enables complex sequence choreography—such as rotating a 3D model, unfurling a UI panel, or triggering a shader transition—in perfect, frame-by-frame synchronization with the user's scroll velocity3. + +### **FLIP Animations and the View Transitions API** + +When orchestrating complex layout changes, such as filtering a project gallery or rearranging a bento-grid layout, developers utilize the FLIP (First, Last, Invert, Play) technique. Native DOM reflows are computationally expensive. Libraries like Framer Motion or the GSAP Flip plugin circumvent this by recording the bounding client rectangle of an element before a layout change (First), measuring its new position after the change (Last), applying a negative CSS transform to visually return the element to its original starting position instantly (Invert), and then smoothly animating the transform values down to zero (Play)42. This ensures that complex bento-grid layouts can shuffle, expand, and contract seamlessly using only GPU-accelerated transform and opacity properties, completely avoiding expensive layout repaints44. +For navigating between entirely different routes, modern web architecture relies on the View Transitions API13. Historically, transitioning between a thumbnail grid on one route and a detailed hero image on another route required complex manual calculations and global state management to animate the shared element across the unmount/mount lifecycle. The View Transitions API simplifies this natively. Developers assign a unique view-transition-name to the specific elements10. When a programmatic navigation occurs via document.startViewTransition(), the browser halts rendering, captures a rasterized screenshot of the old DOM state and the new DOM state, and automatically generates a smooth morphing animation between the two pseudoelements (e.g., ::view-transition-group and ::view-transition-image-pair)10. This creates an app-like continuity where objects persist across route cuts, communicating profound visual continuity to the user10. + +## **Geographic Rendering and Data Visualization** + +For portfolios that require the presentation of complex geographical datasets, autonomous routing paths, or intricate network graphs, standard DOM charting libraries are vastly insufficient. In these scenarios, GPU-accelerated data visualization frameworks are strictly required. +When visualization involves large-scale maps, millions of individual data points, or animated vehicle routing, Deck.gl emerges as the industry standard46. Developed originally by Uber, Deck.gl is an open-source WebGL-powered framework optimized specifically for massive datasets46. It integrates flawlessly with map providers like Mapbox GL JS48. +The integration architecture utilizes WebGL2 interleaved rendering. Interleaving allows Deck.gl data layers to share the exact same depth buffer as the Mapbox base map48. This means that 3D data elements (such as extruded hexbins or paths) will correctly occlude and be occluded by 3D buildings, terrain, and labels provided by Mapbox, resulting in a cohesive, unified 3D world48. To animate logistical optimization results or personal travel histories, the TripsLayer is utilized46. This specialized layer ingests an array of geospatial coordinates with associated UNIX timestamps and renders an animated trail46. By tying a global currentTime uniform to the overarching requestAnimationFrame loop, developers can procedurally draw paths across the globe51. +For visualizing complex non-geospatial relationships—such as the inner workings of a Graph Neural Network (GNN) or a semantic text clustering algorithm—GPU-accelerated force-directed graphs are required. Libraries like Graphology, combined with WebGL rendering engines like Cosmos.gl, provide this capability52. By shifting the physics simulation (calculating the repulsion of thousands of nodes based on Coulomb's Law and the attraction of links based on Hooke's Law) entirely into GPU compute shaders, the browser can smoothly simulate and render tens of thousands of connections in real-time. This enables users to interactively pull, drag, and inspect massive data clusters without freezing the main UI thread52. + +## **The Integration of Artificial Intelligence on the Edge** + +A distinct hallmark of a profoundly advanced technical portfolio is the deployment of Machine Learning and Artificial Intelligence models directly on the client edge. Rather than relying on traditional REST APIs connecting to expensive, latency-prone Python cloud backends, modern architectures execute inference directly inside the user's browser53. This maximizes user privacy, entirely eliminates network request latency during generation, and drastically reduces server infrastructure costs53. +The pipeline for bringing deep learning models to the web begins with exporting a trained model (typically developed in PyTorch) to the ONNX (Open Neural Network Exchange) format54. Using the torch.onnx.export function (specifically with the dynamo=True flag for modern graph capture), the complex Pythonic execution graph is translated into a standardized, language-agnostic binary representation of mathematical operators55. Because the browser's WebGL and WebGPU backends do not support dynamic tensor shapes, the model must strictly be exported with a fixed batch size (e.g., batch\_size=1)56. +The engine powering this inference within the browser is ONNX Runtime Web30. Historically, this runtime relied on WebAssembly (WASM) to execute models on the CPU53. While WASM is highly efficient and utilizes Single Instruction Multiple Data (SIMD) extensions, it is ultimately bound by CPU limitations, making the real-time inference of Large Language Models (LLMs) or complex vision transformers prohibitively slow5. +The architectural breakthrough enabling true AI on the web is the WebGPU Execution Provider (EP)30. By instructing ONNX Runtime to target WebGPU, the mathematical operations of the neural network (primarily massive matrix multiplications) are translated directly into WebGPU compute shaders30. This allows the browser to utilize the native teraflops of the user's discrete or integrated graphics card30. +However, deploying LLMs on the web requires navigating severe hardware constraints. Standard models weigh tens of gigabytes, making them impossible to transmit over a standard web connection53. Therefore, developers must utilize aggressive quantization—reducing the precision of the model's weights from 32-bit floating-point numbers (FP32) down to 8-bit or even 4-bit integers (INT8/INT4)53. This drastically reduces the memory footprint and the bandwidth required to load the model into the GPU VRAM, at the cost of a marginal, often imperceptible drop in output accuracy53. +Furthermore, researchers note that the overhead of WebGPU dispatching can severely bottleneck performance during autoregressive generation. Every single operator in an ONNX graph requires validation and a command buffer submission to the GPU queue, introducing a per-operation overhead of approximately 95 microseconds61. To combat this overhead, advanced machine learning compilers like Apache TVM and frameworks like WebLLM are utilized to fuse multiple operators into single "mega-kernels," drastically reducing the dispatch frequency60. Through these optimizations, models like LLaMA-3.1-8B and Phi-3.5-mini can run locally in the browser, achieving up to 80% of the hardware's native decoding throughput (e.g., 40 to 70 tokens per second)60. For web developers, abstraction libraries such as Transformers.js provide intuitive JavaScript APIs to harness this ONNX/WebGPU pipeline for tasks ranging from sentiment analysis and feature extraction to localized text generation53. + +## **Embedding Mathematical Optimization and Operations Research** + +For portfolios focused on highly technical engineering domains—such as Operations Research, supply chain management, autonomous routing, or industrial scheduling—demonstrating real-time combinatorial optimization is a formidable addition that sets a candidate apart. Unlike Machine Learning, which deals with probabilistic predictions and pattern recognition, Mathematical Optimization deals with finding the absolute mathematical best solution out of a finite but astronomically large set of possibilities (e.g., solving the Vehicle Routing Problem or the Knapsack Problem)46. +Historically, robust optimization engines like Google OR-Tools—which contain state-of-the-art algorithms for Linear Programming, Mixed-Integer Programming, and Constraint Programming—required robust native server environments65. Browsers, lacking direct hardware access and constrained by the single-threaded V8 engine, were fundamentally incompatible with the deep recursion and aggressive memory manipulation utilized in branch-and-bound optimization algorithms65. +The contemporary architectural solution is the cross-compilation of these massive C++ libraries directly into WebAssembly (WASM)6. WebAssembly provides a low-level, assembly-like virtual machine that executes a compact binary format at near-native speeds inside the browser sandbox6. By porting solvers such as CP-SAT (Constraint Programming \- Boolean Satisfiability) and GLOP (Google Linear Optimization Package) to WASM, the browser inherits the capacity to solve NP-hard mathematical problems entirely locally65. +The workflow involves defining variables, constraints, and objective functions within a JavaScript or TypeScript wrapper64. For example, a user interacting with the portfolio could dynamically adjust supply chain parameters on a Deck.gl map. The frontend passes these variables to the WASM-compiled OR-Tools, which constructs a rigorous mathematical model: +![][image1] +![][image2] +![][image3] +The WASM solver explores the search space and returns the optimal route arrays back to the JavaScript thread, which then updates the Deck.gl TripsLayer instantly46. This architecture completely removes the need for a dedicated Python backend, eliminates network latency, and provides the user with an interactive, instant feedback loop for complex algorithmic inputs65. + +### **Server-Side Architecture: FastAPI and WebSockets** + +While WebAssembly brings unprecedented power to the client edge, it is ultimately constrained by the browser's hard memory limits. Massive-scale network flow problems or highly complex heuristic searches requiring gigabytes of RAM may trigger out-of-memory errors within the restricted WASM sandbox65. When targeting mobile devices or handling enterprise-scale matrices, the architecture must revert to a server-client model. +The optimal backend for numerical computing is typically built in Python, utilizing high-performance asynchronous frameworks like FastAPI combined with optimization modeling languages like Pyomo, Highs, or native OR-Tools bindings68. To maintain the illusion of real-time interactivity on the frontend, standard HTTP REST requests are insufficient due to connection overhead and statelessness. Instead, the architecture must establish a persistent, bidirectional WebSocket connection68. +As the user adjusts parameters on the frontend canvas, the differential data is streamed via WebSockets to the Python server. The server executes the optimization model and, crucially, continuously yields intermediate heuristic improvements (callbacks) back through the WebSocket connection before the final optimal solution is found. The frontend consumes this stream, dynamically updating the visualization in real-time, effectively masking the remote computation delay behind smooth, iterative visual updates. + +## **Architectural Synthesis** + +The construction of an award-winning, visually stunning portfolio requires a rigorous, multi-disciplinary approach to web architecture. It demands the synthesis of declarative UI design with low-level graphics programming and high-performance computing. +By strategically selecting a meta-framework based on hydration needs (Astro for modular static sites, Next.js for continuous canvas applications), and diligently separating DOM logic from graphics rendering via the OffscreenCanvas and Web Workers, developers can guarantee flawless performance. The transition to WebGPU and node-based Shader Languages represents the future of visual effects, drastically reducing draw calls and enabling fluid, audio-reactive 3D environments. Furthermore, by embracing WebAssembly and the WebGPU Execution Provider, developers can integrate profound computational capabilities—such as ONNX-based local machine learning inference and combinatorial optimization solvers—directly into the browser. By orchestrating these complex technologies behind the veneer of smooth scroll kinematics and View Transitions, engineers can deliver an immersive digital experience that unequivocally demonstrates their technical mastery on a global scale. + +#### **Works cited** + +> 1. Best Three.js Websites | Web Design Inspiration \- Awwwards, [https://www.awwwards.com/websites/three-js/](https://www.awwwards.com/websites/three-js/) +> 2. Creative Code: CSS, JavaScript, WebGL and Three.js Experiments \- Awwwards, [https://www.awwwards.com/creative-code-css-javascript-webgl-and-three-js-experiments.html](https://www.awwwards.com/creative-code-css-javascript-webgl-and-three-js-experiments.html) +> 3. awwwards · GitHub Topics, [https://github.com/topics/awwwards](https://github.com/topics/awwwards) +> 4. The Story of Technology Built at Active Theory \- Medium, [https://medium.com/active-theory/the-story-of-technology-built-at-active-theory-5d17ae0e3fb4](https://medium.com/active-theory/the-story-of-technology-built-at-active-theory-5d17ae0e3fb4) +> 5. Empowering In-Browser Deep Learning Inference on Edge Devices with Just-in-Time Kernel Optimizations \- arXiv, [https://arxiv.org/html/2309.08978v2](https://arxiv.org/html/2309.08978v2) +> 6. Your First WebAssembly Program and Web App (Written, Tested, and Deployed Entirely in the Web Browser) | Towards Data Science, [https://towardsdatascience.com/your-first-webassembly-program-and-web-app-written-tested-and-deployed-entirely-in-the-web-browser/](https://towardsdatascience.com/your-first-webassembly-program-and-web-app-written-tested-and-deployed-entirely-in-the-web-browser/) +> 7. Next.js vs. Astro in 2026: A full comparison guide \- Vercel, [https://vercel.com/i/astro-vs-next-js](https://vercel.com/i/astro-vs-next-js) +> 8. Why We Transitioned: A Comparison of Next.js vs Astro | by Mina Stankovic | Medium, [https://medium.com/@stankovicd.mina/why-we-transitioned-a-comparison-of-next-js-vs-astro-ab8fcc839484](https://medium.com/@stankovicd.mina/why-we-transitioned-a-comparison-of-next-js-vs-astro-ab8fcc839484) +> 9. Astro vs Next.js: the stack we're betting on for immersive websites (with Sanity), [https://juncastudio.com/news/astro-vs-nextjs-stack-for-immersive-websites](https://juncastudio.com/news/astro-vs-nextjs-stack-for-immersive-websites) +> 10. Guides: View transitions | Next.js, [https://nextjs.org/docs/app/guides/view-transitions](https://nextjs.org/docs/app/guides/view-transitions) +> 11. Canvas rerendering or camera resetting on route change \- Questions \- three.js forum, [https://discourse.threejs.org/t/canvas-rerendering-or-camera-resetting-on-route-change/54963](https://discourse.threejs.org/t/canvas-rerendering-or-camera-resetting-on-route-change/54963) +> 12. Astro vs Next.js in 2026: Which Framework Should You Build With? \- Cosmic JS, [https://www.cosmicjs.com/blog/nextjs-vs-astro-choosing-the-right-framework-for-your-project](https://www.cosmicjs.com/blog/nextjs-vs-astro-choosing-the-right-framework-for-your-project) +> 13. View Transitions \- React Router, [https://reactrouter.com/how-to/view-transitions](https://reactrouter.com/how-to/view-transitions) +> 14. How to build stunning 3D scenes with React Three Fiber \- Varun Vachhar, [https://varun.ca/modular-webgl/](https://varun.ca/modular-webgl/) +> 15. How to use R3F Next.js || Add page transitions with Framer Motion in the Canvas and DOM, [https://eriksachse.medium.com/how-to-use-r3f-next-js-add-page-transitions-with-framer-motion-in-the-canvas-and-dom-95c33c34ce70](https://eriksachse.medium.com/how-to-use-r3f-next-js-add-page-transitions-with-framer-motion-in-the-canvas-and-dom-95c33c34ce70) +> 16. How to fix "THREE.WebGLRenderer: Context Lost" \- Questions, [https://discourse.threejs.org/t/how-to-fix-three-webglrenderer-context-lost/66395](https://discourse.threejs.org/t/how-to-fix-three-webglrenderer-context-lost/66395) +> 17. Context Lost when I route to another page in react three fiber \- three.js forum, [https://discourse.threejs.org/t/context-lost-when-i-route-to-another-page-in-react-three-fiber/61736](https://discourse.threejs.org/t/context-lost-when-i-route-to-another-page-in-react-three-fiber/61736) +> 18. THREE.WebGLRenderer context lost when I route to another page · Issue \#3176 · pmndrs/react-three-fiber \- GitHub, [https://github.com/pmndrs/react-three-fiber/issues/3176](https://github.com/pmndrs/react-three-fiber/issues/3176) +> 19. Using react-router with reconcilers such as react-three-fiber \- Stack Overflow, [https://stackoverflow.com/questions/58504683/using-react-router-with-reconcilers-such-as-react-three-fiber](https://stackoverflow.com/questions/58504683/using-react-router-with-reconcilers-such-as-react-three-fiber) +> 20. GitHub \- molefrog/wouter: A minimalist-friendly \~2.2KB routing for React and Preact, [https://github.com/molefrog/wouter](https://github.com/molefrog/wouter) +> 21. React State Management: Efficient Use of Context, Zustand & Jotai \- International JavaScript Conference, [https://javascript-conference.com/blog/react-state-management-context-zustand-jotai/](https://javascript-conference.com/blog/react-state-management-context-zustand-jotai/) +> 22. using GSAP with Zustand (React state management), [https://gsap.com/community/forums/topic/39604-using-gsap-with-zustand-react-state-management/](https://gsap.com/community/forums/topic/39604-using-gsap-with-zustand-react-state-management/) +> 23. Thread-Safe Canvas: Building Zero-Latency Interfaces with Web Workers \- Perumal Palani, [https://perumalpalani.com/tech/thread-safe-canvas-web-workers/](https://perumalpalani.com/tech/thread-safe-canvas-web-workers/) +> 24. OffscreenCanvas \- Web APIs | MDN, [https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas) +> 25. OffscreenCanvas—speed up your canvas operations with a web worker | Articles, [https://web.dev/articles/offscreen-canvas](https://web.dev/articles/offscreen-canvas) +> 26. Offscreen worker canvas for react-three-fiber \- GitHub, [https://github.com/pmndrs/react-three-offscreen](https://github.com/pmndrs/react-three-offscreen) +> 27. Three.js \- Awwwards, [https://www.awwwards.com/awwwards/collections/three-js/](https://www.awwwards.com/awwwards/collections/three-js/) +> 28. WebGPU \- TechStacks, [https://techstacks.io/tech/webgpu](https://techstacks.io/tech/webgpu) +> 29. Your first WebGPU app \- Codelabs, [https://codelabs.developers.google.com/your-first-webgpu-app](https://codelabs.developers.google.com/your-first-webgpu-app) +> 30. Using WebGPU | onnxruntime, [https://onnxruntime.ai/docs/tutorials/web/ep-webgpu.html](https://onnxruntime.ai/docs/tutorials/web/ep-webgpu.html) +> 31. \< hydra \>, [https://hydra.ojack.xyz/?code=b3NjKCklMEEua2FsZWlkKCklMEEua2FsZWlkKCklMEEua2FsZWlkKCklMEEua2FsZWlkKCklMEEua2FsZWlkKCklMEEua2FsZWlkKCklMEEua2FsZWlkKCklMEElMjAlMjAub3V0KCk=](https://hydra.ojack.xyz/?code=b3NjKCklMEEua2FsZWlkKCklMEEua2FsZWlkKCklMEEua2FsZWlkKCklMEEua2FsZWlkKCklMEEua2FsZWlkKCklMEEua2FsZWlkKCklMEEua2FsZWlkKCklMEElMjAlMjAub3V0KCk%3D) +> 32. The Complete Guide to Three.js Post-Processing in 2026, [https://threejsroadmap.com/blog/the-complete-guide-to-threejs-post-processing-in-2026](https://threejsroadmap.com/blog/the-complete-guide-to-threejs-post-processing-in-2026) +> 33. pmndrs/postprocessing: A post processing library for three.js. \- GitHub, [https://github.com/pmndrs/postprocessing](https://github.com/pmndrs/postprocessing) +> 34. Creating a Fluid Distortion Animation with Three.js \- Codrops, [https://tympanus.net/codrops/2022/04/11/creating-a-fluid-distortion-animation-with-three-js/](https://tympanus.net/codrops/2022/04/11/creating-a-fluid-distortion-animation-with-three-js/) +> 35. Creating a Water-like Distortion Effect with Three.js \- Codrops, [https://tympanus.net/codrops/2019/10/08/creating-a-water-like-distortion-effect-with-three-js/](https://tympanus.net/codrops/2019/10/08/creating-a-water-like-distortion-effect-with-three-js/) +> 36. Coding a 3D Audio Visualizer with Three.js, GSAP & Web Audio API \- daily.dev, [https://daily.dev/posts/coding-a-3d-audio-visualizer-with-three-js-gsap-web-audio-api-qgrwd8t87](https://daily.dev/posts/coding-a-3d-audio-visualizer-with-three-js-gsap-web-audio-api-qgrwd8t87) +> 37. Coding a 3D Audio Visualizer with Three.js, GSAP & Web Audio API \- Codrops, [https://tympanus.net/codrops/2025/06/18/coding-a-3d-audio-visualizer-with-three-js-gsap-web-audio-api/](https://tympanus.net/codrops/2025/06/18/coding-a-3d-audio-visualizer-with-three-js-gsap-web-audio-api/) +> 38. equirectangular panorama \- three.js webgl, [https://threejs.org/examples/webgl\_panorama\_equirectangular.html](https://threejs.org/examples/webgl_panorama_equirectangular.html) +> 39. Three.js Orbit Controls Playground Demo, [https://threejsdemos.com/demos/camera/orbit](https://threejsdemos.com/demos/camera/orbit) +> 40. Lenis – Smooth Scroll, [https://lenis.dev/](https://lenis.dev/) +> 41. ScrollTrigger with Lenis smooth scroll \- GSAP, [https://gsap.com/community/forums/topic/34814-scrolltrigger-with-lenis-smooth-scroll-problem-with-the-scrollerproxy-setup/](https://gsap.com/community/forums/topic/34814-scrolltrigger-with-lenis-smooth-scroll-problem-with-the-scrollerproxy-setup/) +> 42. Animating Responsive Grid Layout Transitions with GSAP Flip \- Codrops, [https://tympanus.net/codrops/2026/01/20/animating-responsive-grid-layout-transitions-with-gsap-flip/](https://tympanus.net/codrops/2026/01/20/animating-responsive-grid-layout-transitions-with-gsap-flip/) +> 43. Interactive UI Animations with GSAP & Framer Motion | by Toukir Ahamed Pigeon | Medium, [https://medium.com/@toukir.ahamed.pigeon/interactive-ui-animations-with-gsap-framer-motion-f2765ae8a051](https://medium.com/@toukir.ahamed.pigeon/interactive-ui-animations-with-gsap-framer-motion-f2765ae8a051) +> 44. Browse thousands of Bento Grid Animation images for design inspiration | Dribbble, [https://dribbble.com/search/bento-grid-animation](https://dribbble.com/search/bento-grid-animation) +> 45. React bento grid and feature sections | Motion UI, [https://motion.dev/ui/bento-grids](https://motion.dev/ui/bento-grids) +> 46. TripsLayer | deck.gl, [https://deck.gl/docs/api-reference/geo-layers/trips-layer](https://deck.gl/docs/api-reference/geo-layers/trips-layer) +> 47. Showcase | deck.gl, [https://deck.gl/showcase](https://deck.gl/showcase) +> 48. Using with Mapbox \- Deck.gl, [https://deck.gl/docs/developer-guide/base-maps/using-with-mapbox](https://deck.gl/docs/developer-guide/base-maps/using-with-mapbox) +> 49. deck.gl and Mapbox GL JS: Better Together | by Xiaoji Chen | 消极 | vis.gl \- Medium, [https://medium.com/vis-gl/deckgl-and-mapbox-better-together-47b29d6d4fb1](https://medium.com/vis-gl/deckgl-and-mapbox-better-together-47b29d6d4fb1) +> 50. Google Maps and deck.gl Trips Layer | Maps JavaScript API \- Google for Developers, [https://developers.google.com/maps/documentation/javascript/examples/deckgl-tripslayer](https://developers.google.com/maps/documentation/javascript/examples/deckgl-tripslayer) +> 51. deck.gl \+ tripslayer without react.js \- javascript \- Stack Overflow, [https://stackoverflow.com/questions/53243436/deck-gl-tripslayer-without-react-js](https://stackoverflow.com/questions/53243436/deck-gl-tripslayer-without-react-js) +> 52. cosmosgl/graph: GPU-accelerated force graph layout and rendering \- GitHub, [https://github.com/cosmosgl/graph](https://github.com/cosmosgl/graph) +> 53. Run PyTorch models on the edge \- ONNX Runtime, [https://onnxruntime.ai/blogs/pytorch-on-the-edge](https://onnxruntime.ai/blogs/pytorch-on-the-edge) +> 54. Example Deployment Using ONNX — NVIDIA TensorRT, [https://docs.nvidia.com/deeplearning/tensorrt/latest/getting-started/quick-start-onnx-deployment.html](https://docs.nvidia.com/deeplearning/tensorrt/latest/getting-started/quick-start-onnx-deployment.html) +> 55. Export a PyTorch model to ONNX, [https://docs.pytorch.org/tutorials/beginner/onnx/export\_simple\_model\_to\_onnx\_tutorial.html](https://docs.pytorch.org/tutorials/beginner/onnx/export_simple_model_to_onnx_tutorial.html) +> 56. From PyTorch to Production: Deploy ML Models Locally with ONNX (Zero Infrastructure Cost) | Gian Paolo Santopaolo, [https://genmind.ch/posts/From-PyTorch-to-Production-Deploy-ML-Models-Locally-with-ONNX/](https://genmind.ch/posts/From-PyTorch-to-Production-Deploy-ML-Models-Locally-with-ONNX/) +> 57. leestott/OnnxRuntime-webgpu \- GitHub, [https://github.com/leestott/OnnxRuntime-webgpu](https://github.com/leestott/OnnxRuntime-webgpu) +> 58. Benchmarking cross‑platform AI: Web Assembly, ONNX Runtime and TVM for Real‑Time Web, Mobile, and IoT Deployment, [https://wjarr.com/sites/default/files/fulltext\_pdf/WJARR-2025-1832.pdf](https://wjarr.com/sites/default/files/fulltext_pdf/WJARR-2025-1832.pdf) +> 59. Confusing WebGPU with WASM: Clarifying the Two Pillars of Web Performance \- Zenn, [https://zenn.dev/jnch/articles/94543ab2fce1f3?locale=en](https://zenn.dev/jnch/articles/94543ab2fce1f3?locale=en) +> 60. WebLLM: A High-Performance In-Browser LLM Inference Engine \- arXiv, [https://arxiv.org/html/2412.15803v2](https://arxiv.org/html/2412.15803v2) +> 61. Characterizing WebGPU Dispatch Overhead for LLM Inference Across Four GPU Vendors, Three Backends, and Three Browsers \- arXiv, [https://arxiv.org/pdf/2604.02344](https://arxiv.org/pdf/2604.02344) +> 62. Characterizing WebGPU Dispatch Overhead for LLM Inference Across Four GPU Vendors, Three Backends, and Three Browsers \- arXiv, [https://arxiv.org/html/2604.02344v1](https://arxiv.org/html/2604.02344v1) +> 63. Performance of ONNXRuntime WebGPU | by Wei Lu \- Medium, [https://medium.com/@GenerationAI/performance-of-onnxruntime-webgpu-44a25d9897a9](https://medium.com/@GenerationAI/performance-of-onnxruntime-webgpu-44a25d9897a9) +> 64. Axelwickm/or-tools-wasm: Running Google OR-Tools multithreaded in browser through wasm. \- GitHub, [https://github.com/Axelwickm/or-tools-wasm](https://github.com/Axelwickm/or-tools-wasm) +> 65. Compiling Google OR-Tools to WebAssembly Simplifies Browser-Based Optimization Solvers \- DEV Community, [https://dev.to/pavkode/compiling-google-or-tools-to-webassembly-simplifies-browser-based-optimization-solvers-60k](https://dev.to/pavkode/compiling-google-or-tools-to-webassembly-simplifies-browser-based-optimization-solvers-60k) +> 66. I made Google OR-Tools solvers compile to WASM so you can solve more complex optimization problems fully in the browser : r/javascript \- Reddit, [https://www.reddit.com/r/javascript/comments/1tzpcam/i\_made\_google\_ortools\_solvers\_compile\_to\_wasm\_so/](https://www.reddit.com/r/javascript/comments/1tzpcam/i_made_google_ortools_solvers_compile_to_wasm_so/) +> 67. A Suduko Solving Serverless Endpoint Using WebAssembly and OR-Tools \- Medium, [https://medium.com/swlh/a-suduko-solving-serverless-endpoint-using-webassembly-and-or-tools-df9f7bb10044](https://medium.com/swlh/a-suduko-solving-serverless-endpoint-using-webassembly-and-or-tools-df9f7bb10044) +> 68. Low Latency Video Streaming Application with FastApi \- Medium, [https://medium.com/@praveen06061995/low-latency-video-streaming-application-with-fastapi-4c69f5124fda](https://medium.com/@praveen06061995/low-latency-video-streaming-application-with-fastapi-4c69f5124fda) +> 69. Python Websockets with FastAPI and Pydantic (1) \- YouTube, [https://www.youtube.com/watch?v=wgontWHx4jw](https://www.youtube.com/watch?v=wgontWHx4jw) + +[image1]: + +[image2]: + +[image3]: \ No newline at end of file From 3f5f2b7140295a5d5e999f79e1bd72372d42d48d Mon Sep 17 00:00:00 2001 From: ACFHarbinger Date: Sat, 8 Aug 2026 10:59:23 +0100 Subject: [PATCH 02/27] Prune template scaffolding not applicable to this repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These files came from a generic polyglot project template (Tauri-App-Template) and assumed a multi-language monorepo with Python/Kotlin/Java/Rust/Go/C++ modules, cloud infra, and multi-host CI. This repo is a single-language Next.js/React/TypeScript static site deployed to GitHub Pages, plus a small Python/uv notebooks workspace for report research — so the irrelevant pieces are removed: - .forgejo/, .gitea/, .gitlab/: not hosted on those platforms - cloud/, infra/: no cloud/container infra for a static export site - git/: GitHub Project board automation and generic template CONTRIBUTING - docs/{ARCHITECTURE,BENCHMARKS,DEPENDENCIES,DEPENDENCY_POLICY, DEVELOPMENT,DOCUMENTATION_STANDARDS,GLOSSARY,TESTING,TROUBLESHOOTING}.md, docs/adr/, docs/moon/{ROADMAP,CHANGELOG}.md, docs/moon/roadmaps/: described modules/roadmaps that don't exist here - .agent/rules/{go,kotlin,java,cpp,rust,ai_ml}.md, .agent/workflows/{rust,data_eng,ai_ml,deployment_ops}.md, .agent/skills/add-language-module.md, .agent/prompts/cli_generation.md: languages/workflows unused here The two genuine research write-ups under docs/moon/research and docs/moon/reports move to docs/research/ since they're real content about this project, not template placeholders. --- .agent/AGENTS.md | 74 +++++++++ .agent/chat/claude_subagent_delegation.md | 28 ++++ .agent/chat/gemini_subagent_delegation.md | 28 ++++ .agent/claude/chatgpt_subagent_delegation.md | 29 ++++ .agent/claude/gemini_subagent_delegation.md | 29 ++++ .agent/gemini/chatgpt_subagent_delegation.md | 30 ++++ .agent/gemini/claude_subagent_delegation.md | 29 ++++ .agent/prompts/architecture_analysis.md | 9 ++ .agent/prompts/debug.md | 9 ++ .agent/prompts/documentation_update.md | 9 ++ .agent/prompts/feature_implementation.md | 9 ++ .agent/prompts/master_context.md | 10 ++ .agent/prompts/refactory_safety.md | 9 ++ .agent/rules/code_refactor.md | 7 + .agent/rules/code_review.md | 7 + .agent/rules/error_debug.md | 7 + .agent/rules/gui_dev.md | 7 + .agent/rules/python.md | 9 ++ .agent/rules/reasoning_planning.md | 7 + .agent/rules/test_writing.md | 7 + .agent/rules/typescript_react.md | 8 + .agent/skills/build-and-test.md | 12 ++ .agent/skills/debug-crash.md | 7 + .agent/workflows/code_refactor.md | 7 + .agent/workflows/code_review.md | 8 + .agent/workflows/error_debug.md | 8 + .agent/workflows/gui_dev.md | 7 + .agent/workflows/reasoning_planning.md | 7 + .agent/workflows/test_writing.md | 7 + .agent/workflows/typescript_react.md | 7 + .devcontainer/Dockerfile | 34 +++++ .devcontainer/devcontainer.json | 39 +++++ .dockerignore | 45 ++++++ .github/ISSUE_TEMPLATE/bug_report.yml | 49 ++++++ .github/ISSUE_TEMPLATE/config.yml | 8 + .github/ISSUE_TEMPLATE/feature_request.yml | 34 +++++ .github/PULL_REQUEST_TEMPLATE.md | 30 ++++ .github/workflows/agent_sync.yml | 78 ++++++++++ .github/workflows/benchmark.yml | 21 +++ .github/workflows/ci.yml | 108 ++++++++++++++ .github/workflows/docs.yml | 32 ++++ .github/workflows/security.yml | 38 +++++ .pre-commit-config.yaml | 140 ++++++++++++++++++ CLAUDE.md | 5 + GEMINI.md | 5 + ...ractive-portfolio-website-architecture.md} | 0 ...-preferences-and-advanced-technologies.md} | 0 47 files changed, 1097 insertions(+) create mode 100644 .agent/AGENTS.md create mode 100644 .agent/chat/claude_subagent_delegation.md create mode 100644 .agent/chat/gemini_subagent_delegation.md create mode 100644 .agent/claude/chatgpt_subagent_delegation.md create mode 100644 .agent/claude/gemini_subagent_delegation.md create mode 100644 .agent/gemini/chatgpt_subagent_delegation.md create mode 100644 .agent/gemini/claude_subagent_delegation.md create mode 100644 .agent/prompts/architecture_analysis.md create mode 100644 .agent/prompts/debug.md create mode 100644 .agent/prompts/documentation_update.md create mode 100644 .agent/prompts/feature_implementation.md create mode 100644 .agent/prompts/master_context.md create mode 100644 .agent/prompts/refactory_safety.md create mode 100644 .agent/rules/code_refactor.md create mode 100644 .agent/rules/code_review.md create mode 100644 .agent/rules/error_debug.md create mode 100644 .agent/rules/gui_dev.md create mode 100644 .agent/rules/python.md create mode 100644 .agent/rules/reasoning_planning.md create mode 100644 .agent/rules/test_writing.md create mode 100644 .agent/rules/typescript_react.md create mode 100644 .agent/skills/build-and-test.md create mode 100644 .agent/skills/debug-crash.md create mode 100644 .agent/workflows/code_refactor.md create mode 100644 .agent/workflows/code_review.md create mode 100644 .agent/workflows/error_debug.md create mode 100644 .agent/workflows/gui_dev.md create mode 100644 .agent/workflows/reasoning_planning.md create mode 100644 .agent/workflows/test_writing.md create mode 100644 .agent/workflows/typescript_react.md create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .dockerignore create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/agent_sync.yml create mode 100644 .github/workflows/benchmark.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/security.yml create mode 100644 .pre-commit-config.yaml create mode 100644 CLAUDE.md create mode 100644 GEMINI.md rename docs/{moon/research/Global Interactive Portfolio Website Architecture.md => research/global-interactive-portfolio-website-architecture.md} (100%) rename docs/{moon/reports/Website Preferences and Advanced Technologies.md => research/website-preferences-and-advanced-technologies.md} (100%) diff --git a/.agent/AGENTS.md b/.agent/AGENTS.md new file mode 100644 index 0000000..e417a31 --- /dev/null +++ b/.agent/AGENTS.md @@ -0,0 +1,74 @@ +# AGENTS.md - Instructions for Coding Assistant LLMs + +[![Python](https://img.shields.io/badge/Python-3.11+-3776ab?logo=python&logoColor=white)](https://www.python.org/) +[![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) +[![Kotlin](https://img.shields.io/badge/Kotlin-7F52FF?logo=kotlin&logoColor=white)](https://kotlinlang.org/) +[![Java](https://img.shields.io/badge/Java-21-ED8B00?logo=openjdk&logoColor=white)](https://www.java.com/) +[![Rust](https://img.shields.io/badge/Rust-1.80%2B-000000?logo=rust&logoColor=white)](https://www.rust-lang.org/) +[![Go](https://img.shields.io/badge/Go-1.22+-00ADD8?logo=go&logoColor=white)](https://go.dev/) +[![C++](https://img.shields.io/badge/C%2B%2B-17-00599C?logo=cplusplus&logoColor=white)](https://isocpp.org/) + +> **Version**: 1.0 +> **Last Updated**: 2026-07-30 +> **Purpose**: Authoritative reference for AI assistants (Claude, GPT, Gemini, Copilot, etc.) working in repositories generated from this template. + +## Table of Contents + +1. [Project Overview & Mission](#1-project-overview--mission) +2. [Technical Stack & Governance](#2-technical-stack--governance) +3. [Module Boundaries](#3-module-boundaries) +4. [Key CLI Entry Points](#4-key-cli-entry-points) +5. [Coding Standards](#5-coding-standards) +6. [Known Constraints](#6-known-constraints) + +## 1. Project Overview & Mission + +> **TODO:** Replace with a one-paragraph description of what the generated project does and why it exists. + +This repository is a scaffold, not a product. When it is used via "Use this template", update this section first — every other document under `.agent/` and `docs/` links back to it. + +## 2. Technical Stack & Governance + +| Component | Specification | Notes | +| --- | --- | --- | +| Python | 3.11+ | Managed via `uv`; always `source .venv/bin/activate` | +| TypeScript | 5 | Managed via `npm` workspaces | +| Kotlin | 2.0 / JVM 21 | Built via Gradle (`gradlew`) | +| Java | 21 | Built via Maven (`mvn`) | +| Rust | stable | Managed via `cargo` | +| Go | 1.22+ | Managed via `go.mod`, no external tooling required | +| C++ | 17 | Built via CMake, environment managed by Pixi or vcpkg | +| Config | `.env` / `configs/` | Environment-specific values never committed; see `.env.example` | + +## 3. Module Boundaries + +- `python/src` — domain logic. No imports from `typescript/`, `kotlin/`, `java/`, or other language modules. +- `typescript/src` — presentation/CLI layer. Talks to other modules only through their published APIs (HTTP, FFI, or CLI), never by reaching into their source trees. +- `rust/src` and `cpp/src` — performance-critical cores, exposed to higher-level languages through explicit bindings (e.g. `pyo3`/`pybind11`, `napi`, JNI). No language-specific logic should leak across the binding boundary. +- `go/cmd` and `go/internal` — services/CLIs. `internal/` is never imported from outside the `go/` module. +- `kotlin/src` — JVM/Android-facing code, isolated behind Gradle module boundaries. +- `java/src` — JVM-facing code, isolated behind Maven module boundaries. +- Cross-module contracts (schemas, protobufs, OpenAPI specs) live under `docs/` or a dedicated `schemas/` directory — never duplicated per language. + +## 4. Key CLI Entry Points + +| Command | Purpose | +| --- | --- | +| `just --list` | List all available command-runner recipes | +| `just test` | Run the test suite for every language module | +| `just lint` | Run linters/formatters for every language module | +| `just docs` | Build the MkDocs + Sphinx documentation site | +| `just docker-up` | Start the local Docker Compose stack | + +## 5. Coding Standards + +- Follow the per-language rules in [`.agent/rules/`](rules/) (`python.md`, `typescript_react.md`, `kotlin.md`, `java.md`, `rust.md`, `go.md`, `cpp.md`). +- Prefer small, reviewable diffs. Do not reformat files unrelated to the change. +- Every new public function/class needs a docstring/doc-comment; every new module needs at least one test. +- Never commit secrets. Use `.env` (git-ignored) and document new variables in `.env.example`. + +## 6. Known Constraints + +> **TODO:** Document real constraints (rate limits, hardware requirements, licensing restrictions, etc.) once the project has them. + +- This template repository does not build or run as-is — each language module contains only illustrative examples. diff --git a/.agent/chat/claude_subagent_delegation.md b/.agent/chat/claude_subagent_delegation.md new file mode 100644 index 0000000..9c06135 --- /dev/null +++ b/.agent/chat/claude_subagent_delegation.md @@ -0,0 +1,28 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: CLAUDE + +**Identity & Capability:** +You can deploy a Claude AI subagent via the local terminal using the `claude` CLI command. Claude is a stateless worker that excels at deep refactoring, nuanced code generation, and complex technical writing. + +**When to Delegate:** +Invoke the Claude subagent for: +* **Deep Refactoring:** Managing strict memory constraints or borrow-checker rules in Rust or C++. +* **UI/Frontend Generation:** Generating structured code for complex layouts (e.g., TypeScript/React/Tauri dashboards). +* **Granular Code Review:** Performing rigorous audits of custom algorithms or FFmpeg/OpenCV binding logic. + +**Execution Syntax:** +Run the command in your shell, wrapping the prompt in strong quotes. +`claude 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to Claude):** +1. **Complete Independence:** Claude cannot read your memory. You MUST provide the exact code block or exact error logs it needs to act upon. +2. **ReAct / CoT Triggers:** Instruct Claude to use `` XML blocks to plan its refactoring steps before outputting code. +3. **Strict Boundaries:** Specify exact input and output formats (e.g., "Output ONLY valid Rust code inside a single markdown block"). + +**Example Usage:** +`claude 'Act as an expert computer vision engineer. Refactor the following Rust OpenCV bindings used for stitching digital anime images (not camera photos) into a continuous panorama. Ensure memory safety and optimize the feature matching step. Wrap your reasoning in tags, then provide the refactored code. Code to refactor: [INSERT_CODE_HERE]'` + +**Failure Modes to Avoid:** +* **Do not** include single quotes inside the prompt string without escaping them. +* **Do not** delegate tasks that require multi-turn conversational context. +--- \ No newline at end of file diff --git a/.agent/chat/gemini_subagent_delegation.md b/.agent/chat/gemini_subagent_delegation.md new file mode 100644 index 0000000..e7e23e9 --- /dev/null +++ b/.agent/chat/gemini_subagent_delegation.md @@ -0,0 +1,28 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: GEMINI + +**Identity & Capability:** +You have the authority to spawn a Gemini AI subagent via the terminal using the `agy` CLI command. Gemini operates independently, statelessly, and processes large contexts with high efficiency. + +**When to Delegate:** +Invoke the Gemini subagent for: +* **Data Pipeline Construction:** Standardizing complex data transformations or parsing large JSON/log files. +* **Cross-Language Boilerplate:** Generating binding architectures (e.g., PySide6 to C++, or Python-Rust via Maturin). +* **System Architecture:** Extracting structured metrics or designing anomaly detection frameworks from raw execution logs. + +**Execution Syntax:** +Execute the command in your terminal. Ensure the prompt is enclosed in single quotes. +`agy 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to Gemini):** +1. **Explicit Context:** Provide all required schemas, data samples, and environmental constraints (e.g., Linux, KDE, specific GPU hardware). +2. **Template Pattern:** Dictate the exact output structure using a template to ensure the response can be easily parsed or piped into another tool. +3. **Action-Oriented Verbs:** Start instructions with clear directives like "Analyze," "Generate," or "Extract." + +**Example Usage:** +`agy 'Act as an expert Python GUI developer. Generate a PySide6 layout component for a simulation dashboard. The component must display a Budget Report using a bold styling effect that is subtle, without splitting a single text label into half-bold segments. Constraints: 1. Use object-oriented design. 2. Output only the Python class. Context: [INSERT_CURRENT_UI_STATE]'` + +**Failure Modes to Avoid:** +* **Do not** use unescaped single quotes in the `agy` execution string. +* **Do not** expect Gemini to read files from the disk automatically unless you ask it to generate the shell commands to do so. +--- \ No newline at end of file diff --git a/.agent/claude/chatgpt_subagent_delegation.md b/.agent/claude/chatgpt_subagent_delegation.md new file mode 100644 index 0000000..b07dad2 --- /dev/null +++ b/.agent/claude/chatgpt_subagent_delegation.md @@ -0,0 +1,29 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: CHATGPT + +**Identity & Capability:** +You can orchestrate a ChatGPT AI subagent via your local terminal using the `chatgpt` CLI command. ChatGPT acts as a stateless, highly capable reasoning engine. It does not share your context window. + +**When to Delegate:** +Invoke the ChatGPT subagent for: +* **Mathematical Formulations:** Drafting complex formal definitions or LaTeX structures for operations research problems. +* **Literature & Concept Mapping:** Summarizing classical algorithms or mapping metaheuristics to metaphorical variants. +* **Creative Brainstorming:** Generating varied architectural approaches before you commit to writing the implementation code. + +**Execution Syntax:** +Run the command in your shell, wrapping the prompt in strong quotes to prevent shell evaluation errors. +`chatgpt 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to ChatGPT):** +1. **Zero-Shot Context:** You MUST include all necessary definitions, constraints, and current state. +2. **Constraint Pattern:** Explicitly list what ChatGPT must *not* do to keep the response focused and token-efficient. +3. **Role Definition:** Always assign ChatGPT a clear persona (e.g., "Act as a PhD-level Operations Research scientist"). + +**Example Usage:** +`chatgpt 'Act as an Operations Research expert. I am building a solver for the Periodic Capacitated Vehicle Routing Problem (PCVRP). Provide the formal mathematical formulation for the objective function minimizing total travel cost over a multi-day horizon. Rules: 1. Use standard OR notation. 2. Define all variables clearly. 3. Output ONLY the formulation and variable definitions in plain text.'` + +**Failure Modes to Avoid:** +* **Do not** use unescaped single quotes inside the `chatgpt` command string. +* **Do not** assume ChatGPT knows our current project state. +* **Do not** delegate tasks requiring direct file manipulation. +--- \ No newline at end of file diff --git a/.agent/claude/gemini_subagent_delegation.md b/.agent/claude/gemini_subagent_delegation.md new file mode 100644 index 0000000..4798399 --- /dev/null +++ b/.agent/claude/gemini_subagent_delegation.md @@ -0,0 +1,29 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: GEMINI + +**Identity & Capability:** +You have the ability to spawn an independent Gemini AI subagent via your local terminal using the `agy` CLI command. Gemini does not share your context window; it acts as a stateless, highly capable assistant. + +**When to Delegate:** +Invoke the Gemini subagent for: +* **Alternative Reasoning:** Generating a second opinion on complex logic (e.g., operations research heuristics). +* **Parallel Processing:** Delegating isolated sub-tasks (e.g., drafting a regex, summarizing a log file) while you focus on the main architecture. +* **Data Pipeline/Optimization Insights:** Leveraging Gemini's strong capabilities in standardizing data transformations or generating boilerplate for complex data structures. + +**Execution Syntax:** +Run the command in your shell, wrapping the prompt in strong quotes to prevent shell evaluation errors. +`agy 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to Gemini):** +1. **Zero-Shot Context:** You MUST include all necessary code, logs, or context in your string. Gemini cannot see your current workspace unless explicitly provided in the prompt. +2. **Explicit Formatting:** Tell Gemini exactly how to output the response (e.g., "Return ONLY raw JSON," or "Provide a Markdown table"). +3. **Role Definition:** Assign Gemini a role if helpful (e.g., "Act as an expert Rust systems programmer..."). + +**Example Usage:** +`agy 'Act as an expert in combinatorial optimization. Review the following Python logic for a matheuristic routing solver and identify potential bottlenecks in the node selection phase. Return only a bulleted list of 3 specific optimizations: [INSERT_LOGIC_HERE]'` + +**Failure Modes to Avoid:** +* **Do not** use unescaped single quotes inside the `agy` command string. +* **Do not** assume Gemini knows the history of our conversation. +* **Do not** delegate tasks that require writing files directly, unless you instruct Gemini to output the exact terminal commands for you to run. +--- \ No newline at end of file diff --git a/.agent/gemini/chatgpt_subagent_delegation.md b/.agent/gemini/chatgpt_subagent_delegation.md new file mode 100644 index 0000000..040e9f4 --- /dev/null +++ b/.agent/gemini/chatgpt_subagent_delegation.md @@ -0,0 +1,30 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: CHATGPT + +**Identity & Capability:** +You are equipped to launch a ChatGPT AI subagent via the `chatgpt` command. ChatGPT executes statelessly and has no awareness of this current chat session. + +**When to Delegate:** +Invoke the ChatGPT subagent for: +* **Algorithm Conceptualization:** Designing evolutionary operators or Contextual Multi-Armed Bandit (CMAB) reward logic. +* **Documentation & Abstraction:** Generating clear, high-level summaries of complex system state machines or behavioral trees. +* **Alternative Paradigms:** Asking for a completely different approach to a problem when current models are stuck in local optima. + +**Execution Syntax:** +Execute the command in your terminal environment. Always enclose the prompt in single quotes. +`chatgpt 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to ChatGPT):** +1. **Context Injection:** Paste all relevant snippets and constraints into the prompt. +2. **Structured Output:** Use the Template Pattern. Define exactly how the output should look using a mock structure. +3. **Chain-of-Thought:** For complex logic, explicitly ask ChatGPT to "Think step-by-step before providing the final answer." + +**Example Usage:** +`chatgpt 'Act as a Reinforcement Learning specialist. I need to design a reward function for a hierarchical RL model managing daily waste collection routing. Think step-by-step about how to balance route efficiency with temporal constraints. Output format: +## Reasoning: [Step-by-step thoughts] +## Reward Function Logic: [Bullet points]'` + +**Failure Modes to Avoid:** +* **Do not** nest quotes improperly (e.g., `chatgpt 'He said 'hello''`). +* **Do not** use ambiguous instructions; be explicit about the domain. +--- \ No newline at end of file diff --git a/.agent/gemini/claude_subagent_delegation.md b/.agent/gemini/claude_subagent_delegation.md new file mode 100644 index 0000000..19efc7a --- /dev/null +++ b/.agent/gemini/claude_subagent_delegation.md @@ -0,0 +1,29 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: CLAUDE + +**Identity & Capability:** +You are equipped with a CLI tool to orchestrate a Claude AI subagent via the `claude` command. Claude executes statelessly and has no awareness of this current chat session or your previous outputs. + +**When to Delegate:** +Invoke the Claude subagent for: +* **Deep Refactoring:** Restructuring complex C/C++ or Rust codebases where nuanced memory management or borrow-checker rules apply. +* **UI/Frontend Generation:** Generating structured code for visually complex layouts (e.g., TypeScript/React dashboard components for data visualization). +* **Code Review:** Performing rigorous, independent audits of training infrastructure or reinforcement learning algorithms. + +**Execution Syntax:** +Execute the command in your terminal environment. Always enclose the prompt in single quotes to protect shell formatting. +`claude 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to Claude):** +1. **Context Injection:** You MUST paste all relevant snippets, constraints, and current state into the prompt. +2. **Strict Boundaries:** Clearly define what Claude should NOT do to save processing time (e.g., "Do not write explanations, output only the refactored C++ header file"). +3. **Step-by-Step Prompting:** For complex tasks, instruct Claude to use a `` XML block before providing the final answer. + +**Example Usage:** +`claude 'You are an expert TypeScript developer. Below is a data structure representing intermediate output from a Deep Learning model (attention maps). Write a well-typed TS interface for this data, and generate a generic React component to render it. Wrap your reasoning in tags and output the code in a single markdown block. Context: [INSERT_STRUCTURE_HERE]'` + +**Failure Modes to Avoid:** +* **Do not** nest quotes improperly (e.g., `claude 'He said 'hello''`). +* **Do not** ask Claude to perform actions it cannot do (like interacting with your local file system directly). You must parse its text output and perform the file operations yourself. +* **Do not** use ambiguous instructions; quantify your requests (e.g., "Provide exactly 2 solutions"). +--- \ No newline at end of file diff --git a/.agent/prompts/architecture_analysis.md b/.agent/prompts/architecture_analysis.md new file mode 100644 index 0000000..c123d69 --- /dev/null +++ b/.agent/prompts/architecture_analysis.md @@ -0,0 +1,9 @@ +# Prompt: Architecture Analysis + +Given a request to analyze or propose architecture changes: + +1. Read `docs/ARCHITECTURE.md` and `docs/structurizr/` (if present) for the current system model. +2. Identify the module boundaries affected and cross-reference `.agent/AGENTS.md` §3. +3. Present trade-offs (at least two options) rather than a single prescriptive answer, unless the choice is clear-cut. +4. Note migration cost and backward-compatibility impact for any proposed change. +5. If the analysis leads to a decision, record it as an ADR under `docs/adr/`. diff --git a/.agent/prompts/debug.md b/.agent/prompts/debug.md new file mode 100644 index 0000000..a7f7364 --- /dev/null +++ b/.agent/prompts/debug.md @@ -0,0 +1,9 @@ +# Prompt: Debug an Issue + +Given a bug report: + +1. Reproduce it first — do not attempt a fix from the description alone. +2. Follow `.agent/workflows/error_debug.md`. +3. Identify the minimal root cause; explain why the current code produces the wrong result. +4. Fix and add a regression test. +5. Report back with: root cause, fix, and what the regression test now guards against. diff --git a/.agent/prompts/documentation_update.md b/.agent/prompts/documentation_update.md new file mode 100644 index 0000000..84e1aeb --- /dev/null +++ b/.agent/prompts/documentation_update.md @@ -0,0 +1,9 @@ +# Prompt: Documentation Update + +Given a request to update documentation: + +1. Identify every doc surface affected: `README.md`, `docs/*.md`, `.agent/AGENTS.md`, inline docstrings/doc-comments. +2. Match the existing tone and structure of the surrounding document — don't introduce a new format for one section. +3. Verify code examples actually run against the current codebase before including them. +4. Update the table of contents/navigation (`docs/mkdocs.yml`) if a page was added, removed, or renamed. +5. Check for now-stale links elsewhere in the repo that reference the changed section. diff --git a/.agent/prompts/feature_implementation.md b/.agent/prompts/feature_implementation.md new file mode 100644 index 0000000..c99c9ce --- /dev/null +++ b/.agent/prompts/feature_implementation.md @@ -0,0 +1,9 @@ +# Prompt: Feature Implementation + +Given a feature request: + +1. Restate the feature as concrete acceptance criteria. +2. Identify which language module(s) it touches and read `.agent/rules/.md` for each. +3. Propose the smallest design that satisfies the criteria; flag any ambiguity as a question rather than assuming. +4. Implement with tests written alongside the code, not after. +5. Update `docs/` and `docs/moon/CHANGELOG.md` if the feature is user-visible. diff --git a/.agent/prompts/master_context.md b/.agent/prompts/master_context.md new file mode 100644 index 0000000..86024ec --- /dev/null +++ b/.agent/prompts/master_context.md @@ -0,0 +1,10 @@ +# Master Context Prompt + +You are working in a repository generated from `Tauri-App-Template`. Before making changes: + +1. Read `.agent/AGENTS.md` for the project's stack, module boundaries, and coding standards. +2. Read `docs/ARCHITECTURE.md` for the system design. +3. Check `docs/moon/ROADMAP.md` for planned work that might conflict with the requested change. +4. Follow the per-topic rules in `.agent/rules/` and the workflow in `.agent/workflows/` matching the task type. + +If any of the above are still template placeholders (marked `TODO`), say so rather than inventing project details. diff --git a/.agent/prompts/refactory_safety.md b/.agent/prompts/refactory_safety.md new file mode 100644 index 0000000..bfadb00 --- /dev/null +++ b/.agent/prompts/refactory_safety.md @@ -0,0 +1,9 @@ +# Prompt: Safe Refactor + +Given a refactor request: + +1. Confirm test coverage exists for the code being touched; add characterization tests first if not. +2. Follow `.agent/workflows/code_refactor.md` — mechanical changes only, no behavior changes in the same commit. +3. Run the full test suite for the affected module(s) before and after. +4. List every call site updated, so the diff is auditable against the stated scope. +5. Flag anything that looked risky enough to warrant a human second look. diff --git a/.agent/rules/code_refactor.md b/.agent/rules/code_refactor.md new file mode 100644 index 0000000..a181657 --- /dev/null +++ b/.agent/rules/code_refactor.md @@ -0,0 +1,7 @@ +# Code Refactoring Rules + +- Refactors must not change observable behavior in the same change as a behavior fix — separate the two into different commits/PRs. +- Run the existing test suite before and after; a refactor that requires rewriting tests to pass is probably a behavior change in disguise. +- Prefer extracting/renaming over rewriting from scratch — smaller diffs are easier to review and revert. +- Remove dead code you find along the way instead of leaving it "just in case"; git history is the safety net. +- Don't introduce a new abstraction for a pattern used only once or twice — wait for a third occurrence. diff --git a/.agent/rules/code_review.md b/.agent/rules/code_review.md new file mode 100644 index 0000000..a1fc9cf --- /dev/null +++ b/.agent/rules/code_review.md @@ -0,0 +1,7 @@ +# Code Review Rules + +- Review for correctness first, then simplification, then style — don't bikeshed formatting on a PR with a real bug. +- Flag missing test coverage for new branches/edge cases explicitly, with a concrete failing scenario, not just "add more tests." +- Call out security-sensitive changes (auth, input parsing, secrets, deserialization) even if outside the PR's stated scope. +- Prefer suggesting the specific fix over describing the problem abstractly — reviewers should be able to act on a comment without re-deriving it. +- Approve when the change is a net improvement, not only when it is perfect. diff --git a/.agent/rules/error_debug.md b/.agent/rules/error_debug.md new file mode 100644 index 0000000..bc90c0f --- /dev/null +++ b/.agent/rules/error_debug.md @@ -0,0 +1,7 @@ +# Error Handling & Debugging Rules + +- Reproduce the bug with a failing test before fixing it, when practical; the test stays in the suite afterward as a regression guard. +- Fix root causes, not symptoms — do not silence an exception/warning just to make a run "pass." +- Include enough context in error messages to debug from logs alone: what operation failed, on what input, and why. +- When a fix touches shared/critical code, check for other callers relying on the old (broken) behavior before changing it. +- Never use bare `except:`/catch-all handlers that swallow errors silently; log or re-raise. diff --git a/.agent/rules/gui_dev.md b/.agent/rules/gui_dev.md new file mode 100644 index 0000000..f9c5a17 --- /dev/null +++ b/.agent/rules/gui_dev.md @@ -0,0 +1,7 @@ +# GUI Development Rules + +- Keep UI components presentation-only; business logic belongs in a service/store layer that the UI calls into, so it stays testable without a rendered UI. +- Every new interactive control needs a keyboard-accessible path and an accessible name/label — not just a mouse affordance. +- Long-running work (>100ms) must run off the UI thread (worker thread, async task, or backend call) with visible progress/cancellation. +- Match the existing design system/component library before introducing a new one-off style. +- Add at least one component/integration test per new screen or panel. diff --git a/.agent/rules/python.md b/.agent/rules/python.md new file mode 100644 index 0000000..14eb6e5 --- /dev/null +++ b/.agent/rules/python.md @@ -0,0 +1,9 @@ +# Python Rules + +- Target Python 3.11+. Use `uv` for dependency management (`uv sync`, `uv add`, `uv run`). +- Format and lint with `ruff` (`ruff format`, `ruff check --fix`). Do not hand-format code that `ruff format` would rewrite. +- Type-check with `mypy`; new modules should be clean under `--strict` where practical. +- Use `pathlib.Path` instead of `os.path`; use `dataclasses` or `pydantic` models instead of loose dicts for structured data. +- Tests live under `python/test/`, mirroring the `python/src/` package layout. Shared fixtures go in `python/test/conftest.py`. +- Prefer dependency injection over module-level singletons so components stay testable. +- Log with the standard `logging` module (never `print`) using module-level loggers (`logging.getLogger(__name__)`). diff --git a/.agent/rules/reasoning_planning.md b/.agent/rules/reasoning_planning.md new file mode 100644 index 0000000..1de1d40 --- /dev/null +++ b/.agent/rules/reasoning_planning.md @@ -0,0 +1,7 @@ +# Reasoning & Planning Rules + +- Before editing more than one file, state the plan in one short paragraph: what changes, why, and what could break. +- Prefer the smallest change that fully solves the stated problem; do not bundle unrelated refactors into the same change. +- When a task is ambiguous, ask a clarifying question rather than guessing at scope — especially for anything destructive or hard to reverse. +- Re-read the relevant code before editing it, even if it was read earlier in the session; assumptions about file contents go stale. +- When a plan changes mid-task, say so explicitly rather than silently pivoting. diff --git a/.agent/rules/test_writing.md b/.agent/rules/test_writing.md new file mode 100644 index 0000000..4338b19 --- /dev/null +++ b/.agent/rules/test_writing.md @@ -0,0 +1,7 @@ +# Test Writing Rules + +- One assertion concept per test; name tests after the behavior under test (`test___`), not the implementation. +- Cover the happy path, at least one edge case, and at least one failure case for every new public function. +- Prefer real objects/fixtures over mocks; mock only true external boundaries (network, filesystem, clock, randomness). +- Tests must be deterministic — no reliance on wall-clock time, network access, or test execution order. +- A failing test's assertion message should make the failure diagnosable without opening a debugger. diff --git a/.agent/rules/typescript_react.md b/.agent/rules/typescript_react.md new file mode 100644 index 0000000..b1e4144 --- /dev/null +++ b/.agent/rules/typescript_react.md @@ -0,0 +1,8 @@ +# TypeScript / React Rules + +- Target TypeScript 5, `strict: true` in `tsconfig.json`. No `any` without a `// TODO` explaining why. +- Format/lint with `eslint` + `prettier` (or the project's configured equivalents); run `npm run lint` before committing. +- Prefer function components with hooks; colocate a component's styles/tests next to the component file. +- State management: local `useState`/`useReducer` first; reach for a store (Zustand/Redux) only when state is shared across distant components. +- Tests live under `typescript/test/`, using Vitest + Testing Library. Snapshot tests are a last resort, not a default. +- Never call browser/Node-only APIs from shared code that also runs in a Tauri/Electron `src-tauri` context without a platform check. diff --git a/.agent/skills/build-and-test.md b/.agent/skills/build-and-test.md new file mode 100644 index 0000000..08637a5 --- /dev/null +++ b/.agent/skills/build-and-test.md @@ -0,0 +1,12 @@ +# Skill: Build and Test Everything + +Run the full build + test cycle across every language module. + +```bash +just lint # runs ruff/eslint/ktlint/clippy/gofmt/clang-format across all modules +just test # runs pytest/vitest/gradle test/cargo test/go test/ctest across all modules +just docs # builds the MkDocs + Sphinx documentation site +``` + +Use this before opening a PR, or whenever asked to "make sure everything still works." +Report which module(s) failed and the first failing assertion/error, not just "tests failed." diff --git a/.agent/skills/debug-crash.md b/.agent/skills/debug-crash.md new file mode 100644 index 0000000..3d74395 --- /dev/null +++ b/.agent/skills/debug-crash.md @@ -0,0 +1,7 @@ +# Skill: Debug a Crash + +1. Get a reliable repro command and the full crash output (stack trace, exit code, core dump if native code). +2. For native modules (rust/, cpp/), rebuild with debug symbols and reproduce under a debugger (`lldb`/`gdb`) or `rust-gdb`. +3. For managed runtimes (python/, typescript/, kotlin/, java/, go/), capture the full traceback — do not summarize it. +4. Bisect via `git bisect` if the crash is a regression against a known-good commit. +5. Once fixed, add a regression test that would have caught it, and note the root cause in the commit message. diff --git a/.agent/workflows/code_refactor.md b/.agent/workflows/code_refactor.md new file mode 100644 index 0000000..f5113e2 --- /dev/null +++ b/.agent/workflows/code_refactor.md @@ -0,0 +1,7 @@ +# Workflow: Refactoring + +1. Confirm test coverage exists for the code being refactored; add characterization tests first if it doesn't. +2. Make one mechanical change at a time (rename, extract function, move file) and re-run tests after each. +3. Never mix a refactor with a behavior change — split into separate commits if both are needed. +4. Update all call sites and documentation references in the same change as the rename/move. +5. Delete now-dead code rather than commenting it out. diff --git a/.agent/workflows/code_review.md b/.agent/workflows/code_review.md new file mode 100644 index 0000000..f688dc0 --- /dev/null +++ b/.agent/workflows/code_review.md @@ -0,0 +1,8 @@ +# Workflow: Code Review + +1. Read the diff in full before commenting — don't review file-by-file in isolation when a change spans multiple files. +2. Run the test suite and linters locally (or confirm CI is green) before starting a substantive review. +3. Check the change against `.agent/rules/code_review.md` and the relevant language rule file. +4. Leave findings ranked by severity: correctness/security first, then design, then style/nits. +5. For each finding, state the concrete failure scenario (input → wrong output), not just "this looks wrong." +6. Approve once blocking issues are resolved; don't hold a PR hostage over nits. diff --git a/.agent/workflows/error_debug.md b/.agent/workflows/error_debug.md new file mode 100644 index 0000000..fad1d76 --- /dev/null +++ b/.agent/workflows/error_debug.md @@ -0,0 +1,8 @@ +# Workflow: Debugging an Error + +1. Reproduce the failure deterministically — capture the exact input, command, and environment. +2. Read the full stack trace/log before forming a hypothesis; don't guess from the last line alone. +3. Bisect: comment out / isolate halves of the suspect code path until the failure localizes to a small region. +4. Write a minimal failing test that reproduces the bug in isolation. +5. Fix the root cause, confirm the new test passes, then run the broader suite for regressions. +6. Document the root cause and fix in the commit message — not just "fixed bug." diff --git a/.agent/workflows/gui_dev.md b/.agent/workflows/gui_dev.md new file mode 100644 index 0000000..b006817 --- /dev/null +++ b/.agent/workflows/gui_dev.md @@ -0,0 +1,7 @@ +# Workflow: GUI Feature + +1. Sketch the component tree and identify what state lives where (local vs. shared/store). +2. Build the presentational component first with mock data/props, verify it renders correctly. +3. Wire it to real data/state; handle loading, empty, and error states explicitly. +4. Add keyboard navigation and accessible labels. +5. Add a component/integration test, then manually exercise the golden path and at least one edge case in a running instance. diff --git a/.agent/workflows/reasoning_planning.md b/.agent/workflows/reasoning_planning.md new file mode 100644 index 0000000..c629313 --- /dev/null +++ b/.agent/workflows/reasoning_planning.md @@ -0,0 +1,7 @@ +# Workflow: Planning a Non-Trivial Task + +1. Restate the goal in one sentence and identify the acceptance criteria. +2. List the files/modules likely to change and any that are risky to touch. +3. Break the work into independently testable steps; identify which steps can run in parallel. +4. Flag ambiguities and decisions that belong to the user rather than guessing. +5. Execute step by step, checking off progress and adjusting the plan when new information appears. diff --git a/.agent/workflows/test_writing.md b/.agent/workflows/test_writing.md new file mode 100644 index 0000000..cbe6f62 --- /dev/null +++ b/.agent/workflows/test_writing.md @@ -0,0 +1,7 @@ +# Workflow: Writing Tests + +1. Identify the unit under test and its public contract (inputs, outputs, side effects, error modes). +2. Write the happy-path test first, then edge cases (empty input, boundary values, max size), then failure cases. +3. Run the new test and confirm it fails for the right reason before implementing the fix/feature. +4. Implement, then re-run until green. Run the full module's test suite to check for regressions. +5. Check coverage of the new/changed lines; add tests for any branch left uncovered that matters. diff --git a/.agent/workflows/typescript_react.md b/.agent/workflows/typescript_react.md new file mode 100644 index 0000000..9849fef --- /dev/null +++ b/.agent/workflows/typescript_react.md @@ -0,0 +1,7 @@ +# Workflow: TypeScript/React Feature + +1. Define the component's props/types first; let TypeScript surface integration issues before runtime. +2. Build and test the component in isolation (e.g. via a story or a standalone test) before wiring it into a page. +3. Run `tsc --noEmit` and the linter before committing. +4. Add a Vitest + Testing Library test covering render, interaction, and at least one error/empty state. +5. Verify in a running dev server, not just in tests. diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..6d1f6ae --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,34 @@ +# Dev Container image: Python base plus the toolchains for the other +# five template languages (TypeScript, Kotlin, Rust, Go, C++). +FROM python:3.11-slim + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y \ + curl \ + git \ + build-essential \ + cmake \ + ninja-build \ + pkg-config \ + nodejs \ + npm \ + golang \ + openjdk-21-jdk-headless \ + && rm -rf /var/lib/apt/lists/* + +# uv for fast Python package management +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +# Rust toolchain +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable +ENV PATH="/root/.cargo/bin:${PATH}" + +# Create a non-root user for development +RUN useradd -m -s /bin/bash vscode +USER vscode +WORKDIR /app + +ENV PATH="/app/python/.venv/bin:$PATH" + +CMD ["/bin/bash"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..f1af438 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,39 @@ +{ + "name": "Tauri-App-Template Dev Environment", + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance", + "charliermarsh.ruff", + "ms-python.mypy-type-checker", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "rust-lang.rust-analyzer", + "golang.go", + "fwcd.kotlin", + "ms-vscode.cpptools", + "ms-azuretools.vscode-docker" + ], + "settings": { + "python.defaultInterpreterPath": "/app/python/.venv/bin/python", + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.ruff": "explicit", + "source.organizeImports.ruff": "explicit" + }, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff" + } + } + } + }, + "postCreateCommand": "cd python && uv sync --all-extras --dev && cd .. && pre-commit install", + "remoteUser": "vscode" +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9f3ba20 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,45 @@ +# Keep the Docker build context small and reproducible. + +# VCS / CI +.git +.github +.gitignore + +# Python caches / envs +.venv +**/__pycache__ +*.py[cod] +.mypy_cache +.ruff_cache +.pytest_cache +*.egg-info + +# Node / TypeScript +**/node_modules +typescript/dist + +# Build outputs +build/ +site/ +target/ +dist/ +*.so + +# Rust / Cargo +Cargo.lock +.cargo + +# Kotlin / Gradle +.gradle +kotlin/build + +# Editors / OS +.vscode +.idea +.DS_Store + +# Docs build output +docs/sphinx/_build + +# Secrets +.env diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..58c11e9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,49 @@ +name: 🐛 Bug Report +description: Report a defect (structured for humans and coding agents) +title: "[Bug]: " +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: | + Thanks for the report. This is a polyglot template — telling us which module is + affected speeds up triage a lot. + + - type: dropdown + id: module + attributes: + label: Affected module + description: Which part of the codebase? + options: + - Python (python/) + - TypeScript (typescript/) + - Kotlin (kotlin/) + - Rust (rust/) + - Go (go/) + - C++ (cpp/) + - Build / tooling (just, docker, CI) + - Other / unknown + validations: + required: true + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Expected vs actual behavior. + validations: + required: true + + - type: textarea + id: repro + attributes: + label: Steps to reproduce + description: Minimal, exact steps — commands, inputs, environment. + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant log output + render: shell diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..e019844 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: 📖 Documentation + url: https://github.com/ACFHarbinger/Tauri-App-Template/tree/main/docs + about: Architecture, development, testing, and troubleshooting guides. + - name: 🗺️ Roadmap + url: https://github.com/ACFHarbinger/Tauri-App-Template/tree/main/docs/moon + about: Master roadmap and changelog. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..c3ab889 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,34 @@ +name: ✨ Feature Request +description: Propose a new feature or enhancement +title: "[Feature]: " +labels: ["enhancement", "triage"] +body: + - type: textarea + id: problem + attributes: + label: What problem does this solve? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + validations: + required: false + + - type: dropdown + id: module + attributes: + label: Affected module + options: + - Python (python/) + - TypeScript (typescript/) + - Kotlin (kotlin/) + - Rust (rust/) + - Go (go/) + - C++ (cpp/) + - Build / tooling (just, docker, CI) + - Other / unknown + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..0d879b1 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,30 @@ +# Pull Request + +## Summary + + + +## Affected Module(s) + +- [ ] Python (`python/`) +- [ ] TypeScript (`typescript/`) +- [ ] Kotlin (`kotlin/`) +- [ ] Rust (`rust/`) +- [ ] Go (`go/`) +- [ ] C++ (`cpp/`) +- [ ] Tooling / docs / CI + +## Type of Change + +- [ ] 🐛 Bug fix +- [ ] ✨ New feature +- [ ] ♻️ Refactor +- [ ] ⚡ Performance +- [ ] 📚 Documentation +- [ ] 🔧 Tooling / CI + +## Verification + +- [ ] `just lint` and `just test` pass for the affected module(s). +- [ ] Benchmarks included for performance-sensitive changes. +- [ ] Docs / roadmap / `docs/moon/CHANGELOG.md` updated where the public surface changed. diff --git a/.github/workflows/agent_sync.yml b/.github/workflows/agent_sync.yml new file mode 100644 index 0000000..ef95d9d --- /dev/null +++ b/.github/workflows/agent_sync.yml @@ -0,0 +1,78 @@ +name: Agent Backlog Sync + +# Keeps a GitHub Project (V2) board in sync with docs/moon/ROADMAP.md / +# docs/moon/CHANGELOG.md by orchestrating github/scripts/sync_backlog.py. Runs on +# pushes to main that touch either source-of-truth doc, or on demand via +# workflow_dispatch for maintainers who want to force a resync without a doc +# edit. +# +# Adapted from Visual-Graph-Programming's .github/workflows/agent_sync.yml — +# same architecture, generalized off that repo's hardcoded VGP_* naming. + +on: + push: + branches: [main] + paths: + - "docs/moon/ROADMAP.md" + - "docs/moon/CHANGELOG.md" + workflow_dispatch: + inputs: + dry_run: + description: "Print the proposed diff without mutating the board" + type: boolean + default: false + +permissions: + contents: read + issues: write + pull-requests: write + +concurrency: + group: agent-backlog-sync + cancel-in-progress: false + +jobs: + sync: + name: Sync backlog to Project board + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + + - name: Install automation dependencies + run: pip install pyyaml requests google-genai + + - name: Validate automation config + run: | + python -c "import json,pathlib; json.loads(pathlib.Path('github/config/project_labels.json').read_text())" + python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('github/config/automation_rules.yaml').read_text())" + + - name: Run backlog sync + env: + GITHUB_TOKEN: ${{ secrets.PROJECT_AUTOMATION_TOKEN }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + PROJECT_ID: ${{ vars.PROJECT_ID }} + run: | + ARGS="--repo-owner ${{ github.repository_owner }} \ + --repo-name $(basename ${{ github.repository }}) \ + --project-id ${PROJECT_ID}" + if [ "${{ inputs.dry_run }}" = "true" ]; then + ARGS="$ARGS --dry-run" + fi + python github/scripts/sync_backlog.py $ARGS + + - name: Summarize run + if: always() + run: | + echo "### Agent Backlog Sync" >> "$GITHUB_STEP_SUMMARY" + echo "Trigger: ${{ github.event_name }}" >> "$GITHUB_STEP_SUMMARY" + echo "Dry run: ${{ inputs.dry_run || 'false' }}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000..2653151 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,21 @@ +name: Benchmark + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "**/benchmark/**" + - "**/benches/**" + +jobs: + benchmark: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo bench + working-directory: rust + - uses: astral-sh/setup-uv@v5 + - run: uv sync --group dev && uv run pytest benchmark --benchmark-only + working-directory: python diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2bb3efd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,108 @@ +name: CI + +on: + workflow_dispatch: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint-test-python: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --group dev + working-directory: python + - run: uv run ruff check . + working-directory: python + - run: uv run mypy src + working-directory: python + - run: uv run pytest test -v --cov=src --cov-report=xml + working-directory: python + - uses: codecov/codecov-action@v4 + with: + files: python/coverage.xml + fail_ci_if_error: false + + lint-test-typescript: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: typescript/package-lock.json + - run: npm ci + working-directory: typescript + - run: npm run lint + working-directory: typescript + - run: npm test + working-directory: typescript + + lint-test-rust: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - run: cargo fmt -- --check + working-directory: rust + - run: cargo clippy --all-targets -- -D warnings + working-directory: rust + - run: cargo test + working-directory: rust + + lint-test-go: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.22" + - run: gofmt -l . | tee /dev/stderr | (! read) + working-directory: go + - run: go vet ./... + working-directory: go + - run: go test ./... + working-directory: go + + lint-test-kotlin: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "21" + - run: ./gradlew ktlintCheck test + working-directory: kotlin + + lint-test-java: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "21" + cache: maven + cache-dependency-path: java/pom.xml + - run: mvn -B test + working-directory: java + + build-test-cpp: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: | + cmake -S cpp -B cpp/build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON + cmake --build cpp/build --parallel + - run: ctest --test-dir cpp/build --output-on-failure diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..52b3b76 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,32 @@ +name: Docs + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "docs/**" + - "python/validation/docs/**" + - "mkdocs.yml" + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: docs-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv pip install --system mkdocs-material mkdocstrings[python] mkdocs-jupyter + - run: mkdocs build --config-file docs/mkdocs.yml --strict + - uses: actions/upload-pages-artifact@v3 + with: + path: site + - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..4fb5d62 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,38 @@ +name: Security Audit + +on: + workflow_dispatch: + schedule: + - cron: "0 6 * * 1" # every Monday 06:00 UTC + pull_request: + branches: [main] + +jobs: + cargo-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo install cargo-audit --locked + - run: cargo audit + working-directory: rust + + pip-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --group dev + working-directory: python + - run: uv run pip-audit + working-directory: python + + npm-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: npm audit --audit-level=high + working-directory: typescript diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..75aec4e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,140 @@ +# Pre-commit hooks for this repository. +# +# Install once: pip install pre-commit && pre-commit install +# Run manually: pre-commit run --all-files +# Update hooks: pre-commit autoupdate +# +# Gate philosophy: +# - Blocking: ruff, clippy, ktlint, golangci-lint, clang-format, lychee +# - Advisory: mypy, tsc, cargo audit + +repos: + # ── General file hygiene ───────────────────────────────────────────────── + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + args: [--allow-multiple-documents] + - id: check-toml + - id: check-json + - id: check-merge-conflict + - id: check-added-large-files + args: [--maxkb=5000] + - id: mixed-line-ending + args: [--fix=lf] + + # ── Python: ruff (lint + format) ───────────────────────────────────────── + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.0 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + types_or: [python, pyi] + files: ^python/ + - id: ruff-format + types_or: [python, pyi] + files: ^python/ + + # ── Python: mypy (advisory) ────────────────────────────────────────────── + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.10.1 + hooks: + - id: mypy + name: mypy (python/src) + args: [--ignore-missing-imports, --no-error-summary] + files: ^python/src/ + + # ── TypeScript: type check ─────────────────────────────────────────────── + - repo: local + hooks: + - id: tsc-check + name: TypeScript type check (typescript/src) + language: system + entry: bash -c 'cd typescript && npx tsc --noEmit 2>&1' + files: ^typescript/src/.*\.ts$ + pass_filenames: false + + # ── Rust: fmt + clippy ──────────────────────────────────────────────────── + - repo: local + hooks: + - id: cargo-fmt + name: cargo fmt (rust/) + language: system + entry: bash -c 'cd rust && cargo fmt -- --check' + files: ^rust/.*\.rs$ + pass_filenames: false + - id: cargo-clippy + name: cargo clippy (rust/) + language: system + entry: bash -c 'cd rust && cargo clippy --all-targets -- -D warnings' + files: ^rust/.*\.rs$ + pass_filenames: false + + # ── Go: fmt + vet ───────────────────────────────────────────────────────── + - repo: local + hooks: + - id: go-fmt + name: gofmt (go/) + language: system + entry: bash -c 'cd go && gofmt -l .' + files: ^go/.*\.go$ + pass_filenames: false + - id: go-vet + name: go vet (go/) + language: system + entry: bash -c 'cd go && go vet ./...' + files: ^go/.*\.go$ + pass_filenames: false + + # ── Kotlin: ktlint ──────────────────────────────────────────────────────── + - repo: local + hooks: + - id: ktlint + name: ktlint (kotlin/) + language: system + entry: bash -c 'cd kotlin && ./gradlew ktlintCheck' + files: ^kotlin/.*\.kt$ + pass_filenames: false + + # ── Java: Maven compile check ──────────────────────────────────────────── + - repo: local + hooks: + - id: maven-compile + name: mvn compile (java/) + language: system + entry: bash -c 'cd java && mvn -q -B compile' + files: ^java/.*\.java$ + pass_filenames: false + + # ── C++: clang-format ───────────────────────────────────────────────────── + - repo: local + hooks: + - id: clang-format-cpp + name: clang-format (cpp/ sources) + language: system + entry: bash -c 'clang-format --dry-run --Werror "$@"' -- + files: ^cpp/(src|include|test)/.*\.(cpp|hpp|h)$ + pass_filenames: true + + # ── Markdown: link checker ─────────────────────────────────────────────── + - repo: https://github.com/lycheeverse/lychee + rev: lychee-v0.15.1 + hooks: + - id: lychee + name: lychee (markdown link check) + args: + - --verbose + - --no-progress + - --exclude=https://crates.io + - --include-verbatim + files: \.(md|markdown)$ + pass_filenames: true + + # ── Jupyter: strip notebook output from git ────────────────────────────── + - repo: https://github.com/kynan/nbstripout + rev: 0.7.1 + hooks: + - id: nbstripout + files: ^docs/notebooks/.*\.ipynb$ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ee17db1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +[//]: # "Include the content from the master AGENTS file" +[//]: # "Note: This is a pointer to the source of truth" +[//]: # "Content from .agent/AGENTS.md" + +[Include .agent/AGENTS.md](.agent/AGENTS.md) diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..ee17db1 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,5 @@ +[//]: # "Include the content from the master AGENTS file" +[//]: # "Note: This is a pointer to the source of truth" +[//]: # "Content from .agent/AGENTS.md" + +[Include .agent/AGENTS.md](.agent/AGENTS.md) diff --git a/docs/moon/research/Global Interactive Portfolio Website Architecture.md b/docs/research/global-interactive-portfolio-website-architecture.md similarity index 100% rename from docs/moon/research/Global Interactive Portfolio Website Architecture.md rename to docs/research/global-interactive-portfolio-website-architecture.md diff --git a/docs/moon/reports/Website Preferences and Advanced Technologies.md b/docs/research/website-preferences-and-advanced-technologies.md similarity index 100% rename from docs/moon/reports/Website Preferences and Advanced Technologies.md rename to docs/research/website-preferences-and-advanced-technologies.md From 559d105c163d13af70c16961d13b6585437efbdc Mon Sep 17 00:00:00 2001 From: ACFHarbinger Date: Sat, 8 Aug 2026 11:01:09 +0100 Subject: [PATCH 03/27] Adapt CI workflows, templates, pre-commit and devcontainer to this stack - ci.yml: replace the polyglot lint/test/build matrix with lint + tsc + jest + cypress + a next build check, plus a light ruff pass over notebooks/. - security.yml: npm audit + pip-audit (uv) for notebooks/, drop cargo-audit. - Remove agent_sync.yml (GitHub Project board automation tied to the deleted git/ scripts) and docs.yml (no MkDocs site here). - Issue/PR templates: swap the language-module dropdown for the site's actual content areas (posts/reports/projects/tools/etc). - dependabot.yml: add npm and uv (notebooks/) ecosystems alongside the existing github-actions one; bump cadence to weekly. - .pre-commit-config.yaml: eslint/tsc for the site, ruff/mypy for notebooks/, drop the other five language toolchains. - .devcontainer: Node + Python(uv) only, drop Rust/Go/JVM toolchains. - pyproject.toml: fix license file reference (LICENSE, not LICENSE.md). --- .devcontainer/Dockerfile | 23 +---- .devcontainer/devcontainer.json | 22 ++--- .dockerignore | 31 ++---- .github/ISSUE_TEMPLATE/bug_report.yml | 24 ++--- .github/ISSUE_TEMPLATE/config.yml | 12 +-- .github/ISSUE_TEMPLATE/feature_request.yml | 19 ++-- .github/PULL_REQUEST_TEMPLATE.md | 24 ++--- .github/dependabot.yml | 15 ++- .github/workflows/agent_sync.yml | 78 --------------- .github/workflows/benchmark.yml | 21 ---- .github/workflows/ci.yml | 106 +++++++-------------- .github/workflows/docs.yml | 32 ------- .github/workflows/security.yml | 26 ++--- .pre-commit-config.yaml | 106 +++++---------------- pyproject.toml | 2 +- 15 files changed, 138 insertions(+), 403 deletions(-) delete mode 100644 .github/workflows/agent_sync.yml delete mode 100644 .github/workflows/benchmark.yml delete mode 100644 .github/workflows/docs.yml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 6d1f6ae..c2c265e 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,34 +1,21 @@ -# Dev Container image: Python base plus the toolchains for the other -# five template languages (TypeScript, Kotlin, Rust, Go, C++). -FROM python:3.11-slim +# Dev Container image: Node.js for the Next.js site plus a Python +# toolchain for the notebooks/ research workspace. +FROM node:20-slim ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y \ curl \ git \ - build-essential \ - cmake \ - ninja-build \ - pkg-config \ - nodejs \ - npm \ - golang \ - openjdk-21-jdk-headless \ + python3 \ && rm -rf /var/lib/apt/lists/* -# uv for fast Python package management +# uv for fast Python package management (notebooks/) COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ -# Rust toolchain -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable -ENV PATH="/root/.cargo/bin:${PATH}" - # Create a non-root user for development RUN useradd -m -s /bin/bash vscode USER vscode WORKDIR /app -ENV PATH="/app/python/.venv/bin:$PATH" - CMD ["/bin/bash"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f1af438..fd7c08c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,5 @@ { - "name": "Tauri-App-Template Dev Environment", + "name": "github-pages Dev Environment", "build": { "dockerfile": "Dockerfile", "context": ".." @@ -7,26 +7,16 @@ "customizations": { "vscode": { "extensions": [ - "ms-python.python", - "ms-python.vscode-pylance", - "charliermarsh.ruff", - "ms-python.mypy-type-checker", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", - "rust-lang.rust-analyzer", - "golang.go", - "fwcd.kotlin", - "ms-vscode.cpptools", - "ms-azuretools.vscode-docker" + "bradlc.vscode-tailwindcss", + "ms-python.python", + "charliermarsh.ruff" ], "settings": { - "python.defaultInterpreterPath": "/app/python/.venv/bin/python", - "python.testing.pytestEnabled": true, - "python.testing.unittestEnabled": false, "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll.ruff": "explicit", - "source.organizeImports.ruff": "explicit" + "source.fixAll.eslint": "explicit" }, "[python]": { "editor.defaultFormatter": "charliermarsh.ruff" @@ -34,6 +24,6 @@ } } }, - "postCreateCommand": "cd python && uv sync --all-extras --dev && cd .. && pre-commit install", + "postCreateCommand": "npm ci && (cd notebooks && uv sync --extra dev) && pre-commit install", "remoteUser": "vscode" } diff --git a/.dockerignore b/.dockerignore index 9f3ba20..9ccc3e0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,11 +1,16 @@ -# Keep the Docker build context small and reproducible. +# Keep the dev container build context small and reproducible. # VCS / CI .git .github .gitignore -# Python caches / envs +# Node / Next.js +node_modules +.next +out + +# Python (notebooks/) .venv **/__pycache__ *.py[cod] @@ -14,32 +19,10 @@ .pytest_cache *.egg-info -# Node / TypeScript -**/node_modules -typescript/dist - -# Build outputs -build/ -site/ -target/ -dist/ -*.so - -# Rust / Cargo -Cargo.lock -.cargo - -# Kotlin / Gradle -.gradle -kotlin/build - # Editors / OS .vscode .idea .DS_Store -# Docs build output -docs/sphinx/_build - # Secrets .env diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 58c11e9..ed71da4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -6,22 +6,22 @@ body: - type: markdown attributes: value: | - Thanks for the report. This is a polyglot template — telling us which module is - affected speeds up triage a lot. + Thanks for the report. Telling us which page or area is affected speeds up triage. - type: dropdown - id: module + id: area attributes: - label: Affected module - description: Which part of the codebase? + label: Affected area + description: Which part of the site? options: - - Python (python/) - - TypeScript (typescript/) - - Kotlin (kotlin/) - - Rust (rust/) - - Go (go/) - - C++ (cpp/) - - Build / tooling (just, docker, CI) + - Posts + - Reports + - Projects + - Tools + - Media + - About / Other + - Notebooks (notebooks/) + - Build / tooling / CI - Other / unknown validations: required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index e019844..464e151 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: false contact_links: - - name: 📖 Documentation - url: https://github.com/ACFHarbinger/Tauri-App-Template/tree/main/docs - about: Architecture, development, testing, and troubleshooting guides. - - name: 🗺️ Roadmap - url: https://github.com/ACFHarbinger/Tauri-App-Template/tree/main/docs/moon - about: Master roadmap and changelog. + - name: 🌐 Live Site + url: https://acfharbinger.github.io/github-pages/ + about: The deployed site this repository builds. + - name: 📖 Docs + url: https://github.com/ACFHarbinger/github-pages/tree/main/docs + about: Research notes and write-ups backing the site's reports/posts. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index c3ab889..bf3b70e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -18,17 +18,18 @@ body: required: false - type: dropdown - id: module + id: area attributes: - label: Affected module + label: Affected area options: - - Python (python/) - - TypeScript (typescript/) - - Kotlin (kotlin/) - - Rust (rust/) - - Go (go/) - - C++ (cpp/) - - Build / tooling (just, docker, CI) + - Posts + - Reports + - Projects + - Tools + - Media + - About / Other + - Notebooks (notebooks/) + - Build / tooling / CI - Other / unknown validations: required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0d879b1..2765e73 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,29 +2,25 @@ ## Summary - + -## Affected Module(s) +## Affected Area(s) -- [ ] Python (`python/`) -- [ ] TypeScript (`typescript/`) -- [ ] Kotlin (`kotlin/`) -- [ ] Rust (`rust/`) -- [ ] Go (`go/`) -- [ ] C++ (`cpp/`) -- [ ] Tooling / docs / CI +- [ ] Site content (`app/content/**`) +- [ ] Components / styling (`src/`) +- [ ] Notebooks / research (`notebooks/`, `docs/research/`) +- [ ] Tooling / CI / docs ## Type of Change - [ ] 🐛 Bug fix - [ ] ✨ New feature - [ ] ♻️ Refactor -- [ ] ⚡ Performance -- [ ] 📚 Documentation +- [ ] 📚 Content / documentation - [ ] 🔧 Tooling / CI ## Verification -- [ ] `just lint` and `just test` pass for the affected module(s). -- [ ] Benchmarks included for performance-sensitive changes. -- [ ] Docs / roadmap / `docs/moon/CHANGELOG.md` updated where the public surface changed. +- [ ] `npm run lint` and `npm test` pass. +- [ ] `npm run build` succeeds (static export). +- [ ] Manually checked the affected page(s) with `npm run dev`. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8ac6b8c..2e13794 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,19 @@ version: 2 updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + groups: + npm-dependencies: + patterns: ["*"] + + - package-ecosystem: "uv" + directory: "/notebooks" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "monthly" + interval: "weekly" diff --git a/.github/workflows/agent_sync.yml b/.github/workflows/agent_sync.yml deleted file mode 100644 index ef95d9d..0000000 --- a/.github/workflows/agent_sync.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Agent Backlog Sync - -# Keeps a GitHub Project (V2) board in sync with docs/moon/ROADMAP.md / -# docs/moon/CHANGELOG.md by orchestrating github/scripts/sync_backlog.py. Runs on -# pushes to main that touch either source-of-truth doc, or on demand via -# workflow_dispatch for maintainers who want to force a resync without a doc -# edit. -# -# Adapted from Visual-Graph-Programming's .github/workflows/agent_sync.yml — -# same architecture, generalized off that repo's hardcoded VGP_* naming. - -on: - push: - branches: [main] - paths: - - "docs/moon/ROADMAP.md" - - "docs/moon/CHANGELOG.md" - workflow_dispatch: - inputs: - dry_run: - description: "Print the proposed diff without mutating the board" - type: boolean - default: false - -permissions: - contents: read - issues: write - pull-requests: write - -concurrency: - group: agent-backlog-sync - cancel-in-progress: false - -jobs: - sync: - name: Sync backlog to Project board - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - cache: "pip" - - - name: Install automation dependencies - run: pip install pyyaml requests google-genai - - - name: Validate automation config - run: | - python -c "import json,pathlib; json.loads(pathlib.Path('github/config/project_labels.json').read_text())" - python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('github/config/automation_rules.yaml').read_text())" - - - name: Run backlog sync - env: - GITHUB_TOKEN: ${{ secrets.PROJECT_AUTOMATION_TOKEN }} - GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} - PROJECT_ID: ${{ vars.PROJECT_ID }} - run: | - ARGS="--repo-owner ${{ github.repository_owner }} \ - --repo-name $(basename ${{ github.repository }}) \ - --project-id ${PROJECT_ID}" - if [ "${{ inputs.dry_run }}" = "true" ]; then - ARGS="$ARGS --dry-run" - fi - python github/scripts/sync_backlog.py $ARGS - - - name: Summarize run - if: always() - run: | - echo "### Agent Backlog Sync" >> "$GITHUB_STEP_SUMMARY" - echo "Trigger: ${{ github.event_name }}" >> "$GITHUB_STEP_SUMMARY" - echo "Dry run: ${{ inputs.dry_run || 'false' }}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml deleted file mode 100644 index 2653151..0000000 --- a/.github/workflows/benchmark.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Benchmark - -on: - workflow_dispatch: - push: - branches: [main] - paths: - - "**/benchmark/**" - - "**/benches/**" - -jobs: - benchmark: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - run: cargo bench - working-directory: rust - - uses: astral-sh/setup-uv@v5 - - run: uv sync --group dev && uv run pytest benchmark --benchmark-only - working-directory: python diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bb3efd..8936e82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,97 +12,63 @@ concurrency: cancel-in-progress: true jobs: - lint-test-python: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v5 - - run: uv sync --group dev - working-directory: python - - run: uv run ruff check . - working-directory: python - - run: uv run mypy src - working-directory: python - - run: uv run pytest test -v --cov=src --cov-report=xml - working-directory: python - - uses: codecov/codecov-action@v4 - with: - files: python/coverage.xml - fail_ci_if_error: false - - lint-test-typescript: + lint-typecheck-unit: + name: Lint, typecheck & unit tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 20 cache: npm - cache-dependency-path: typescript/package-lock.json - run: npm ci - working-directory: typescript - run: npm run lint - working-directory: typescript - - run: npm test - working-directory: typescript + - run: npx tsc --noEmit + - run: npm test -- --ci --coverage - lint-test-rust: + build: + name: Build static export runs-on: ubuntu-latest + needs: lint-typecheck-unit steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/setup-node@v4 with: - components: rustfmt, clippy - - run: cargo fmt -- --check - working-directory: rust - - run: cargo clippy --all-targets -- -D warnings - working-directory: rust - - run: cargo test - working-directory: rust - - lint-test-go: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - uses: actions/upload-artifact@v4 with: - go-version: "1.22" - - run: gofmt -l . | tee /dev/stderr | (! read) - working-directory: go - - run: go vet ./... - working-directory: go - - run: go test ./... - working-directory: go + name: site-export + path: out/ + retention-days: 3 - lint-test-kotlin: + e2e: + name: Cypress end-to-end runs-on: ubuntu-latest + needs: build steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/setup-node@v4 with: - distribution: temurin - java-version: "21" - - run: ./gradlew ktlintCheck test - working-directory: kotlin - - lint-test-java: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - name: Run Cypress against the static export + uses: cypress-io/github-action@v6 with: - distribution: temurin - java-version: "21" - cache: maven - cache-dependency-path: java/pom.xml - - run: mvn -B test - working-directory: java + start: npm start + wait-on: "http://localhost:3000/github-pages" - build-test-cpp: + notebooks: + name: Lint notebooks workspace runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: | - cmake -S cpp -B cpp/build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON - cmake --build cpp/build --parallel - - run: ctest --test-dir cpp/build --output-on-failure + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + working-directory: notebooks + - run: uv run ruff check . + working-directory: notebooks diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 52b3b76..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Docs - -on: - workflow_dispatch: - push: - branches: [main] - paths: - - "docs/**" - - "python/validation/docs/**" - - "mkdocs.yml" - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: docs-${{ github.ref }} - cancel-in-progress: true - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v5 - - run: uv pip install --system mkdocs-material mkdocstrings[python] mkdocs-jupyter - - run: mkdocs build --config-file docs/mkdocs.yml --strict - - uses: actions/upload-pages-artifact@v3 - with: - path: site - - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 4fb5d62..fcd4bec 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -8,31 +8,21 @@ on: branches: [main] jobs: - cargo-audit: + npm-audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - run: cargo install cargo-audit --locked - - run: cargo audit - working-directory: rust + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm audit --audit-level=high pip-audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v5 - - run: uv sync --group dev - working-directory: python + - run: uv sync --extra dev + working-directory: notebooks - run: uv run pip-audit - working-directory: python - - npm-audit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - - run: npm audit --audit-level=high - working-directory: typescript + working-directory: notebooks diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75aec4e..a5acf57 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,8 +5,8 @@ # Update hooks: pre-commit autoupdate # # Gate philosophy: -# - Blocking: ruff, clippy, ktlint, golangci-lint, clang-format, lychee -# - Advisory: mypy, tsc, cargo audit +# - Blocking: eslint, ruff +# - Advisory: tsc, mypy repos: # ── General file hygiene ───────────────────────────────────────────────── @@ -17,7 +17,6 @@ repos: - id: end-of-file-fixer - id: check-yaml args: [--allow-multiple-documents] - - id: check-toml - id: check-json - id: check-merge-conflict - id: check-added-large-files @@ -25,98 +24,40 @@ repos: - id: mixed-line-ending args: [--fix=lf] - # ── Python: ruff (lint + format) ───────────────────────────────────────── + # ── TypeScript / React: eslint (blocking) ──────────────────────────────── + - repo: local + hooks: + - id: eslint + name: eslint (src/, app/) + language: system + entry: npm run lint -- + pass_filenames: false + - id: tsc-check + name: TypeScript type check (advisory) + language: system + entry: bash -c 'npx tsc --noEmit' + files: \.(ts|tsx)$ + pass_filenames: false + + # ── Python (notebooks/): ruff (blocking) + mypy (advisory) ─────────────── - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.5.0 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] types_or: [python, pyi] - files: ^python/ + files: ^notebooks/ - id: ruff-format types_or: [python, pyi] - files: ^python/ + files: ^notebooks/ - # ── Python: mypy (advisory) ────────────────────────────────────────────── - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.10.1 hooks: - id: mypy - name: mypy (python/src) + name: mypy (notebooks/) args: [--ignore-missing-imports, --no-error-summary] - files: ^python/src/ - - # ── TypeScript: type check ─────────────────────────────────────────────── - - repo: local - hooks: - - id: tsc-check - name: TypeScript type check (typescript/src) - language: system - entry: bash -c 'cd typescript && npx tsc --noEmit 2>&1' - files: ^typescript/src/.*\.ts$ - pass_filenames: false - - # ── Rust: fmt + clippy ──────────────────────────────────────────────────── - - repo: local - hooks: - - id: cargo-fmt - name: cargo fmt (rust/) - language: system - entry: bash -c 'cd rust && cargo fmt -- --check' - files: ^rust/.*\.rs$ - pass_filenames: false - - id: cargo-clippy - name: cargo clippy (rust/) - language: system - entry: bash -c 'cd rust && cargo clippy --all-targets -- -D warnings' - files: ^rust/.*\.rs$ - pass_filenames: false - - # ── Go: fmt + vet ───────────────────────────────────────────────────────── - - repo: local - hooks: - - id: go-fmt - name: gofmt (go/) - language: system - entry: bash -c 'cd go && gofmt -l .' - files: ^go/.*\.go$ - pass_filenames: false - - id: go-vet - name: go vet (go/) - language: system - entry: bash -c 'cd go && go vet ./...' - files: ^go/.*\.go$ - pass_filenames: false - - # ── Kotlin: ktlint ──────────────────────────────────────────────────────── - - repo: local - hooks: - - id: ktlint - name: ktlint (kotlin/) - language: system - entry: bash -c 'cd kotlin && ./gradlew ktlintCheck' - files: ^kotlin/.*\.kt$ - pass_filenames: false - - # ── Java: Maven compile check ──────────────────────────────────────────── - - repo: local - hooks: - - id: maven-compile - name: mvn compile (java/) - language: system - entry: bash -c 'cd java && mvn -q -B compile' - files: ^java/.*\.java$ - pass_filenames: false - - # ── C++: clang-format ───────────────────────────────────────────────────── - - repo: local - hooks: - - id: clang-format-cpp - name: clang-format (cpp/ sources) - language: system - entry: bash -c 'clang-format --dry-run --Werror "$@"' -- - files: ^cpp/(src|include|test)/.*\.(cpp|hpp|h)$ - pass_filenames: true + files: ^notebooks/.*\.py$ # ── Markdown: link checker ─────────────────────────────────────────────── - repo: https://github.com/lycheeverse/lychee @@ -127,7 +68,6 @@ repos: args: - --verbose - --no-progress - - --exclude=https://crates.io - --include-verbatim files: \.(md|markdown)$ pass_filenames: true @@ -137,4 +77,4 @@ repos: rev: 0.7.1 hooks: - id: nbstripout - files: ^docs/notebooks/.*\.ipynb$ + files: ^notebooks/.*\.ipynb$ diff --git a/pyproject.toml b/pyproject.toml index f305c5b..b9f7438 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "github-pages" version = "0.1.0" description = "Python environments for my github-pages website." readme = "README.md" -license = { file = "LICENSE.md" } +license = { file = "LICENSE" } requires-python = ">=3.11" authors = [{ name = "ACFHarbinger" }] dependencies = [] From e825c0666462320b2775b6d96a8e38f5414f72da Mon Sep 17 00:00:00 2001 From: ACFHarbinger Date: Sat, 8 Aug 2026 11:04:38 +0100 Subject: [PATCH 04/27] Rewrite .agent content for this project's actual stack and content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENTS.md, the TS/React and Python rule files, the build-and-test and debug-crash skills, and the master_context/architecture_analysis/ documentation_update/feature_implementation prompts all described a generic polyglot template (multi-language modules, Vitest, Tauri, MkDocs, just recipes). Rewritten to reflect what's actually here: a static-export Next.js/React/TypeScript site with Jest+Cypress tests, and a small notebooks/ Python-uv workspace for report research. Also swapped the domain-specific examples in the six subagent delegation protocol files (Rust/C++ refactors, PySide6 GUIs, RL reward functions, OpenCV bindings) for ones grounded in this repo — drafting report prose, reviewing notebook analysis code, refactoring React components — while keeping the PCVRP example where it already matched an existing report. --- .agent/AGENTS.md | 80 +++++++++----------- .agent/chat/claude_subagent_delegation.md | 10 +-- .agent/chat/gemini_subagent_delegation.md | 8 +- .agent/claude/chatgpt_subagent_delegation.md | 8 +- .agent/claude/gemini_subagent_delegation.md | 10 +-- .agent/gemini/chatgpt_subagent_delegation.md | 10 +-- .agent/gemini/claude_subagent_delegation.md | 10 +-- .agent/prompts/architecture_analysis.md | 7 +- .agent/prompts/documentation_update.md | 7 +- .agent/prompts/feature_implementation.md | 6 +- .agent/prompts/master_context.md | 11 ++- .agent/rules/python.md | 13 ++-- .agent/rules/typescript_react.md | 9 ++- .agent/skills/build-and-test.md | 21 +++-- .agent/skills/debug-crash.md | 12 +-- .gitmessage | 2 + 16 files changed, 113 insertions(+), 111 deletions(-) create mode 100644 .gitmessage diff --git a/.agent/AGENTS.md b/.agent/AGENTS.md index e417a31..02f2397 100644 --- a/.agent/AGENTS.md +++ b/.agent/AGENTS.md @@ -1,74 +1,66 @@ # AGENTS.md - Instructions for Coding Assistant LLMs -[![Python](https://img.shields.io/badge/Python-3.11+-3776ab?logo=python&logoColor=white)](https://www.python.org/) +[![Next.js](https://img.shields.io/badge/Next.js-14-000000?logo=nextdotjs&logoColor=white)](https://nextjs.org/) +[![React](https://img.shields.io/badge/React-18-61DAFB?logo=react&logoColor=white)](https://react.dev/) [![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) -[![Kotlin](https://img.shields.io/badge/Kotlin-7F52FF?logo=kotlin&logoColor=white)](https://kotlinlang.org/) -[![Java](https://img.shields.io/badge/Java-21-ED8B00?logo=openjdk&logoColor=white)](https://www.java.com/) -[![Rust](https://img.shields.io/badge/Rust-1.80%2B-000000?logo=rust&logoColor=white)](https://www.rust-lang.org/) -[![Go](https://img.shields.io/badge/Go-1.22+-00ADD8?logo=go&logoColor=white)](https://go.dev/) -[![C++](https://img.shields.io/badge/C%2B%2B-17-00599C?logo=cplusplus&logoColor=white)](https://isocpp.org/) +[![Tailwind CSS](https://img.shields.io/badge/Tailwind_CSS-3-06B6D4?logo=tailwindcss&logoColor=white)](https://tailwindcss.com/) +[![Python](https://img.shields.io/badge/Python-3.11+-3776ab?logo=python&logoColor=white)](https://www.python.org/) -> **Version**: 1.0 -> **Last Updated**: 2026-07-30 -> **Purpose**: Authoritative reference for AI assistants (Claude, GPT, Gemini, Copilot, etc.) working in repositories generated from this template. +> **Purpose**: Authoritative reference for AI assistants (Claude, GPT, Gemini, Copilot, etc.) working in this repository. ## Table of Contents -1. [Project Overview & Mission](#1-project-overview--mission) -2. [Technical Stack & Governance](#2-technical-stack--governance) +1. [Project Overview](#1-project-overview) +2. [Technical Stack](#2-technical-stack) 3. [Module Boundaries](#3-module-boundaries) -4. [Key CLI Entry Points](#4-key-cli-entry-points) +4. [Key Commands](#4-key-commands) 5. [Coding Standards](#5-coding-standards) 6. [Known Constraints](#6-known-constraints) -## 1. Project Overview & Mission +## 1. Project Overview -> **TODO:** Replace with a one-paragraph description of what the generated project does and why it exists. +This is ACFHarbinger's personal website: a statically-exported Next.js blog/knowledge base covering posts, longer-form reports, project write-ups, tool notes, and media, deployed to GitHub Pages at +[acfharbinger.github.io/github-pages](https://acfharbinger.github.io/github-pages/). Content lives as Markdown under `app/content/
/` and is rendered through the App Router; `notebooks/` is a small, separate Python/uv workspace used to run the analysis behind some reports (e.g. audio signal processing, PCVRP) before writing them up. -This repository is a scaffold, not a product. When it is used via "Use this template", update this section first — every other document under `.agent/` and `docs/` links back to it. - -## 2. Technical Stack & Governance +## 2. Technical Stack | Component | Specification | Notes | | --- | --- | --- | -| Python | 3.11+ | Managed via `uv`; always `source .venv/bin/activate` | -| TypeScript | 5 | Managed via `npm` workspaces | -| Kotlin | 2.0 / JVM 21 | Built via Gradle (`gradlew`) | -| Java | 21 | Built via Maven (`mvn`) | -| Rust | stable | Managed via `cargo` | -| Go | 1.22+ | Managed via `go.mod`, no external tooling required | -| C++ | 17 | Built via CMake, environment managed by Pixi or vcpkg | -| Config | `.env` / `configs/` | Environment-specific values never committed; see `.env.example` | +| Next.js | 14 (App Router, `output: 'export'`) | Static export deployed to GitHub Pages, `basePath: /github-pages` | +| React / TypeScript | 18 / 5 | `strict: true` in `tsconfig.json` | +| Styling | Tailwind CSS 3 | Config in `tailwind.config.js` | +| Content | Markdown + `gray-matter` / `remark` | Parsed at build time from `app/content/
/` | +| Unit tests | Jest + Testing Library | `src/components/__tests__/` | +| E2E tests | Cypress | `cypress/e2e/` | +| Notebooks | Python 3.11+, managed via `uv` | `notebooks/`, workspace member of the root `pyproject.toml` | ## 3. Module Boundaries -- `python/src` — domain logic. No imports from `typescript/`, `kotlin/`, `java/`, or other language modules. -- `typescript/src` — presentation/CLI layer. Talks to other modules only through their published APIs (HTTP, FFI, or CLI), never by reaching into their source trees. -- `rust/src` and `cpp/src` — performance-critical cores, exposed to higher-level languages through explicit bindings (e.g. `pyo3`/`pybind11`, `napi`, JNI). No language-specific logic should leak across the binding boundary. -- `go/cmd` and `go/internal` — services/CLIs. `internal/` is never imported from outside the `go/` module. -- `kotlin/src` — JVM/Android-facing code, isolated behind Gradle module boundaries. -- `java/src` — JVM-facing code, isolated behind Maven module boundaries. -- Cross-module contracts (schemas, protobufs, OpenAPI specs) live under `docs/` or a dedicated `schemas/` directory — never duplicated per language. +- `app/` — Next.js App Router: routes, layouts, and Markdown content under `app/content/
/` (`posts`, `reports`, `projects`, `tools`, `media`, `about`, `other`). +- `src/components/` — presentational and layout React components consumed by `app/`. Business logic (content loading/parsing) belongs in `lib/`, not inline in components. +- `lib/` — server-side helpers (Markdown loading/parsing, front-matter handling) used by `app/` at build time. +- `notebooks/` — independent Python/uv workspace for exploratory analysis backing written reports. Not part of the Next.js build; never imported from `src/`/`app/`. +- `public/` — static assets served as-is. -## 4. Key CLI Entry Points +## 4. Key Commands | Command | Purpose | | --- | --- | -| `just --list` | List all available command-runner recipes | -| `just test` | Run the test suite for every language module | -| `just lint` | Run linters/formatters for every language module | -| `just docs` | Build the MkDocs + Sphinx documentation site | -| `just docker-up` | Start the local Docker Compose stack | +| `npm run dev` | Local dev server | +| `npm run build` | Static export to `out/` | +| `npm run lint` | ESLint (Next.js config) | +| `npm test` / `npm run test:watch` | Jest unit tests | +| `npx cypress open` / `npx cypress run` | Cypress e2e (against a running build/dev server) | +| `cd notebooks && uv sync --extra dev` | Set up the notebooks Python environment | ## 5. Coding Standards -- Follow the per-language rules in [`.agent/rules/`](rules/) (`python.md`, `typescript_react.md`, `kotlin.md`, `java.md`, `rust.md`, `go.md`, `cpp.md`). +- Follow the per-topic rules in [`.agent/rules/`](rules/) (`typescript_react.md`, `python.md`, plus the language-agnostic ones). - Prefer small, reviewable diffs. Do not reformat files unrelated to the change. -- Every new public function/class needs a docstring/doc-comment; every new module needs at least one test. -- Never commit secrets. Use `.env` (git-ignored) and document new variables in `.env.example`. +- New components get a Jest test in `src/components/__tests__/`; new user-facing flows get a Cypress spec in `cypress/e2e/`. +- Never commit secrets. This site has no runtime secrets today — flag it clearly if a change would introduce one. ## 6. Known Constraints -> **TODO:** Document real constraints (rate limits, hardware requirements, licensing restrictions, etc.) once the project has them. - -- This template repository does not build or run as-is — each language module contains only illustrative examples. +- The site is a fully static export (`output: 'export'`) — no server-side code, API routes, or runtime environment variables beyond the build-time `NEXT_PUBLIC_BASE_PATH`. +- `notebooks/` is exploratory/research tooling, not covered by the main CI build; it has its own lint/test story via `uv`. diff --git a/.agent/chat/claude_subagent_delegation.md b/.agent/chat/claude_subagent_delegation.md index 9c06135..3e0fd3f 100644 --- a/.agent/chat/claude_subagent_delegation.md +++ b/.agent/chat/claude_subagent_delegation.md @@ -6,9 +6,9 @@ You can deploy a Claude AI subagent via the local terminal using the `claude` CL **When to Delegate:** Invoke the Claude subagent for: -* **Deep Refactoring:** Managing strict memory constraints or borrow-checker rules in Rust or C++. -* **UI/Frontend Generation:** Generating structured code for complex layouts (e.g., TypeScript/React/Tauri dashboards). -* **Granular Code Review:** Performing rigorous audits of custom algorithms or FFmpeg/OpenCV binding logic. +* **Deep Refactoring:** Restructuring a React/TypeScript component tree in `src/components/` without changing rendered output. +* **UI/Frontend Generation:** Generating structured code for complex layouts (e.g., a new content-listing or data-visualization component). +* **Granular Code Review:** Performing rigorous audits of a notebook's analysis logic before it's cited in a report. **Execution Syntax:** Run the command in your shell, wrapping the prompt in strong quotes. @@ -17,10 +17,10 @@ Run the command in your shell, wrapping the prompt in strong quotes. **Subagent Prompting Rules (How to talk to Claude):** 1. **Complete Independence:** Claude cannot read your memory. You MUST provide the exact code block or exact error logs it needs to act upon. 2. **ReAct / CoT Triggers:** Instruct Claude to use `` XML blocks to plan its refactoring steps before outputting code. -3. **Strict Boundaries:** Specify exact input and output formats (e.g., "Output ONLY valid Rust code inside a single markdown block"). +3. **Strict Boundaries:** Specify exact input and output formats (e.g., "Output ONLY valid TypeScript code inside a single markdown block"). **Example Usage:** -`claude 'Act as an expert computer vision engineer. Refactor the following Rust OpenCV bindings used for stitching digital anime images (not camera photos) into a continuous panorama. Ensure memory safety and optimize the feature matching step. Wrap your reasoning in tags, then provide the refactored code. Code to refactor: [INSERT_CODE_HERE]'` +`claude 'Act as an expert React/TypeScript developer. Refactor the following PostWrapper component so it shares its Markdown-rendering logic with ReportWrapper via a common hook, without changing rendered output. Wrap your reasoning in tags, then provide the refactored code. Code to refactor: [INSERT_CODE_HERE]'` **Failure Modes to Avoid:** * **Do not** include single quotes inside the prompt string without escaping them. diff --git a/.agent/chat/gemini_subagent_delegation.md b/.agent/chat/gemini_subagent_delegation.md index e7e23e9..8c4de37 100644 --- a/.agent/chat/gemini_subagent_delegation.md +++ b/.agent/chat/gemini_subagent_delegation.md @@ -6,9 +6,9 @@ You have the authority to spawn a Gemini AI subagent via the terminal using the **When to Delegate:** Invoke the Gemini subagent for: -* **Data Pipeline Construction:** Standardizing complex data transformations or parsing large JSON/log files. -* **Cross-Language Boilerplate:** Generating binding architectures (e.g., PySide6 to C++, or Python-Rust via Maturin). -* **System Architecture:** Extracting structured metrics or designing anomaly detection frameworks from raw execution logs. +* **Data Wrangling:** Standardizing a notebook's data transformations or parsing large JSON/log/CSV inputs for a report. +* **Front-Matter/Content Boilerplate:** Generating consistent Markdown front-matter and stub pages when adding a new `app/content/
/` entry. +* **Long-Context Extraction:** Pulling structured summaries or key figures out of a long research write-up under `docs/research/`. **Execution Syntax:** Execute the command in your terminal. Ensure the prompt is enclosed in single quotes. @@ -20,7 +20,7 @@ Execute the command in your terminal. Ensure the prompt is enclosed in single qu 3. **Action-Oriented Verbs:** Start instructions with clear directives like "Analyze," "Generate," or "Extract." **Example Usage:** -`agy 'Act as an expert Python GUI developer. Generate a PySide6 layout component for a simulation dashboard. The component must display a Budget Report using a bold styling effect that is subtle, without splitting a single text label into half-bold segments. Constraints: 1. Use object-oriented design. 2. Output only the Python class. Context: [INSERT_CURRENT_UI_STATE]'` +`agy 'Act as an expert technical writer. Given the following notebook analysis output (summary stats + a chart description), draft the "Results" section of a Markdown report for app/content/reports/. Constraints: 1. Match the tone of the site's existing reports. 2. Output only the Markdown section. Context: [INSERT_ANALYSIS_OUTPUT]'` **Failure Modes to Avoid:** * **Do not** use unescaped single quotes in the `agy` execution string. diff --git a/.agent/claude/chatgpt_subagent_delegation.md b/.agent/claude/chatgpt_subagent_delegation.md index b07dad2..8fb228a 100644 --- a/.agent/claude/chatgpt_subagent_delegation.md +++ b/.agent/claude/chatgpt_subagent_delegation.md @@ -6,9 +6,9 @@ You can orchestrate a ChatGPT AI subagent via your local terminal using the `cha **When to Delegate:** Invoke the ChatGPT subagent for: -* **Mathematical Formulations:** Drafting complex formal definitions or LaTeX structures for operations research problems. -* **Literature & Concept Mapping:** Summarizing classical algorithms or mapping metaheuristics to metaphorical variants. -* **Creative Brainstorming:** Generating varied architectural approaches before you commit to writing the implementation code. +* **Mathematical Formulations:** Drafting formal definitions or notation for a report's math section (e.g. the PCVRP report under `app/content/reports/`). +* **Literature & Concept Mapping:** Summarizing background material or related work to cite in a report. +* **Creative Brainstorming:** Generating varied framings for a post/report before committing to a structure. **Execution Syntax:** Run the command in your shell, wrapping the prompt in strong quotes to prevent shell evaluation errors. @@ -20,7 +20,7 @@ Run the command in your shell, wrapping the prompt in strong quotes to prevent s 3. **Role Definition:** Always assign ChatGPT a clear persona (e.g., "Act as a PhD-level Operations Research scientist"). **Example Usage:** -`chatgpt 'Act as an Operations Research expert. I am building a solver for the Periodic Capacitated Vehicle Routing Problem (PCVRP). Provide the formal mathematical formulation for the objective function minimizing total travel cost over a multi-day horizon. Rules: 1. Use standard OR notation. 2. Define all variables clearly. 3. Output ONLY the formulation and variable definitions in plain text.'` +`chatgpt 'Act as an Operations Research expert. I am writing a report on the Periodic Capacitated Vehicle Routing Problem (PCVRP). Provide the formal mathematical formulation for the objective function minimizing total travel cost over a multi-day horizon. Rules: 1. Use standard OR notation. 2. Define all variables clearly. 3. Output ONLY the formulation and variable definitions in plain text.'` **Failure Modes to Avoid:** * **Do not** use unescaped single quotes inside the `chatgpt` command string. diff --git a/.agent/claude/gemini_subagent_delegation.md b/.agent/claude/gemini_subagent_delegation.md index 4798399..d0b8204 100644 --- a/.agent/claude/gemini_subagent_delegation.md +++ b/.agent/claude/gemini_subagent_delegation.md @@ -6,9 +6,9 @@ You have the ability to spawn an independent Gemini AI subagent via your local t **When to Delegate:** Invoke the Gemini subagent for: -* **Alternative Reasoning:** Generating a second opinion on complex logic (e.g., operations research heuristics). -* **Parallel Processing:** Delegating isolated sub-tasks (e.g., drafting a regex, summarizing a log file) while you focus on the main architecture. -* **Data Pipeline/Optimization Insights:** Leveraging Gemini's strong capabilities in standardizing data transformations or generating boilerplate for complex data structures. +* **Alternative Reasoning:** Generating a second opinion on a notebook's analysis approach before it's written up as a report. +* **Parallel Processing:** Delegating isolated sub-tasks (e.g., drafting a regex, summarizing a log file) while you focus on the main change. +* **Long-Context Summarization:** Leveraging Gemini's large context window to distill a long research doc under `docs/research/` into a post-sized outline. **Execution Syntax:** Run the command in your shell, wrapping the prompt in strong quotes to prevent shell evaluation errors. @@ -17,10 +17,10 @@ Run the command in your shell, wrapping the prompt in strong quotes to prevent s **Subagent Prompting Rules (How to talk to Gemini):** 1. **Zero-Shot Context:** You MUST include all necessary code, logs, or context in your string. Gemini cannot see your current workspace unless explicitly provided in the prompt. 2. **Explicit Formatting:** Tell Gemini exactly how to output the response (e.g., "Return ONLY raw JSON," or "Provide a Markdown table"). -3. **Role Definition:** Assign Gemini a role if helpful (e.g., "Act as an expert Rust systems programmer..."). +3. **Role Definition:** Assign Gemini a role if helpful (e.g., "Act as an expert technical editor..."). **Example Usage:** -`agy 'Act as an expert in combinatorial optimization. Review the following Python logic for a matheuristic routing solver and identify potential bottlenecks in the node selection phase. Return only a bulleted list of 3 specific optimizations: [INSERT_LOGIC_HERE]'` +`agy 'Act as an expert in signal processing. Review the following Python analysis code from notebooks/ and identify potential correctness issues in the filtering step. Return only a bulleted list of 3 specific issues: [INSERT_LOGIC_HERE]'` **Failure Modes to Avoid:** * **Do not** use unescaped single quotes inside the `agy` command string. diff --git a/.agent/gemini/chatgpt_subagent_delegation.md b/.agent/gemini/chatgpt_subagent_delegation.md index 040e9f4..0845c68 100644 --- a/.agent/gemini/chatgpt_subagent_delegation.md +++ b/.agent/gemini/chatgpt_subagent_delegation.md @@ -6,9 +6,9 @@ You are equipped to launch a ChatGPT AI subagent via the `chatgpt` command. Chat **When to Delegate:** Invoke the ChatGPT subagent for: -* **Algorithm Conceptualization:** Designing evolutionary operators or Contextual Multi-Armed Bandit (CMAB) reward logic. -* **Documentation & Abstraction:** Generating clear, high-level summaries of complex system state machines or behavioral trees. -* **Alternative Paradigms:** Asking for a completely different approach to a problem when current models are stuck in local optima. +* **Content Drafting:** Drafting or tightening prose for a blog post/report before it's polished and placed under `app/content/`. +* **Documentation & Abstraction:** Generating clear, high-level summaries of a notebook's analysis for the write-up that cites it. +* **Alternative Paradigms:** Asking for a completely different framing of a post/report when the current draft feels stuck. **Execution Syntax:** Execute the command in your terminal environment. Always enclose the prompt in single quotes. @@ -20,9 +20,9 @@ Execute the command in your terminal environment. Always enclose the prompt in s 3. **Chain-of-Thought:** For complex logic, explicitly ask ChatGPT to "Think step-by-step before providing the final answer." **Example Usage:** -`chatgpt 'Act as a Reinforcement Learning specialist. I need to design a reward function for a hierarchical RL model managing daily waste collection routing. Think step-by-step about how to balance route efficiency with temporal constraints. Output format: +`chatgpt 'Act as a technical editor. Below is a draft section of a blog report on audio signal processing. Think step-by-step about where the explanation loses a non-specialist reader before tightening it. Output format: ## Reasoning: [Step-by-step thoughts] -## Reward Function Logic: [Bullet points]'` +## Revised Section: [Rewritten Markdown]'` **Failure Modes to Avoid:** * **Do not** nest quotes improperly (e.g., `chatgpt 'He said 'hello''`). diff --git a/.agent/gemini/claude_subagent_delegation.md b/.agent/gemini/claude_subagent_delegation.md index 19efc7a..a9cdf8f 100644 --- a/.agent/gemini/claude_subagent_delegation.md +++ b/.agent/gemini/claude_subagent_delegation.md @@ -6,9 +6,9 @@ You are equipped with a CLI tool to orchestrate a Claude AI subagent via the `cl **When to Delegate:** Invoke the Claude subagent for: -* **Deep Refactoring:** Restructuring complex C/C++ or Rust codebases where nuanced memory management or borrow-checker rules apply. -* **UI/Frontend Generation:** Generating structured code for visually complex layouts (e.g., TypeScript/React dashboard components for data visualization). -* **Code Review:** Performing rigorous, independent audits of training infrastructure or reinforcement learning algorithms. +* **Deep Refactoring:** Restructuring a React/TypeScript component tree in `src/components/` without changing rendered output. +* **UI/Frontend Generation:** Generating structured code for visually complex layouts (e.g., a new content-listing or data-visualization component). +* **Code Review:** Performing rigorous, independent audits of a notebook's analysis logic before it's cited in a report. **Execution Syntax:** Execute the command in your terminal environment. Always enclose the prompt in single quotes to protect shell formatting. @@ -16,11 +16,11 @@ Execute the command in your terminal environment. Always enclose the prompt in s **Subagent Prompting Rules (How to talk to Claude):** 1. **Context Injection:** You MUST paste all relevant snippets, constraints, and current state into the prompt. -2. **Strict Boundaries:** Clearly define what Claude should NOT do to save processing time (e.g., "Do not write explanations, output only the refactored C++ header file"). +2. **Strict Boundaries:** Clearly define what Claude should NOT do to save processing time (e.g., "Do not write explanations, output only the refactored component file"). 3. **Step-by-Step Prompting:** For complex tasks, instruct Claude to use a `` XML block before providing the final answer. **Example Usage:** -`claude 'You are an expert TypeScript developer. Below is a data structure representing intermediate output from a Deep Learning model (attention maps). Write a well-typed TS interface for this data, and generate a generic React component to render it. Wrap your reasoning in tags and output the code in a single markdown block. Context: [INSERT_STRUCTURE_HERE]'` +`claude 'You are an expert TypeScript/React developer. Below is the shape of the front-matter for a report under app/content/reports/. Write a well-typed TS interface for it, and generate a generic React component to render its metadata card. Wrap your reasoning in tags and output the code in a single markdown block. Context: [INSERT_FRONTMATTER_SHAPE_HERE]'` **Failure Modes to Avoid:** * **Do not** nest quotes improperly (e.g., `claude 'He said 'hello''`). diff --git a/.agent/prompts/architecture_analysis.md b/.agent/prompts/architecture_analysis.md index c123d69..e3fba07 100644 --- a/.agent/prompts/architecture_analysis.md +++ b/.agent/prompts/architecture_analysis.md @@ -2,8 +2,7 @@ Given a request to analyze or propose architecture changes: -1. Read `docs/ARCHITECTURE.md` and `docs/structurizr/` (if present) for the current system model. -2. Identify the module boundaries affected and cross-reference `.agent/AGENTS.md` §3. +1. Read [`.agent/AGENTS.md`](../AGENTS.md) §3 for current module boundaries (`app/`, `src/components/`, `lib/`, `notebooks/`). +2. Identify which of those boundaries the change affects, and whether it's compatible with a static export (`output: 'export'`) — no server runtime. 3. Present trade-offs (at least two options) rather than a single prescriptive answer, unless the choice is clear-cut. -4. Note migration cost and backward-compatibility impact for any proposed change. -5. If the analysis leads to a decision, record it as an ADR under `docs/adr/`. +4. Note the migration cost for existing content/pages if the proposal changes how content is loaded or routed. diff --git a/.agent/prompts/documentation_update.md b/.agent/prompts/documentation_update.md index 84e1aeb..a420a3a 100644 --- a/.agent/prompts/documentation_update.md +++ b/.agent/prompts/documentation_update.md @@ -2,8 +2,7 @@ Given a request to update documentation: -1. Identify every doc surface affected: `README.md`, `docs/*.md`, `.agent/AGENTS.md`, inline docstrings/doc-comments. +1. Identify every doc surface affected: `README.md`, `docs/research/*.md`, `.agent/AGENTS.md`, inline comments. 2. Match the existing tone and structure of the surrounding document — don't introduce a new format for one section. -3. Verify code examples actually run against the current codebase before including them. -4. Update the table of contents/navigation (`docs/mkdocs.yml`) if a page was added, removed, or renamed. -5. Check for now-stale links elsewhere in the repo that reference the changed section. +3. Verify any commands/code examples actually run against the current codebase before including them. +4. Check for now-stale links elsewhere in the repo that reference the changed section. diff --git a/.agent/prompts/feature_implementation.md b/.agent/prompts/feature_implementation.md index c99c9ce..6183777 100644 --- a/.agent/prompts/feature_implementation.md +++ b/.agent/prompts/feature_implementation.md @@ -3,7 +3,7 @@ Given a feature request: 1. Restate the feature as concrete acceptance criteria. -2. Identify which language module(s) it touches and read `.agent/rules/.md` for each. +2. Identify which layer(s) it touches (`app/` routing/content, `src/components/`, `lib/`) and read [`.agent/rules/typescript_react.md`](../rules/typescript_react.md). 3. Propose the smallest design that satisfies the criteria; flag any ambiguity as a question rather than assuming. -4. Implement with tests written alongside the code, not after. -5. Update `docs/` and `docs/moon/CHANGELOG.md` if the feature is user-visible. +4. Implement with tests written alongside the code, not after (Jest for components/logic, Cypress for a new user-facing flow). +5. Confirm the change still builds as a static export (`npm run build`). diff --git a/.agent/prompts/master_context.md b/.agent/prompts/master_context.md index 86024ec..6feec50 100644 --- a/.agent/prompts/master_context.md +++ b/.agent/prompts/master_context.md @@ -1,10 +1,9 @@ # Master Context Prompt -You are working in a repository generated from `Tauri-App-Template`. Before making changes: +You are working in ACFHarbinger's personal website: a statically-exported Next.js/React/TypeScript blog. Before making changes: -1. Read `.agent/AGENTS.md` for the project's stack, module boundaries, and coding standards. -2. Read `docs/ARCHITECTURE.md` for the system design. -3. Check `docs/moon/ROADMAP.md` for planned work that might conflict with the requested change. -4. Follow the per-topic rules in `.agent/rules/` and the workflow in `.agent/workflows/` matching the task type. +1. Read [`.agent/AGENTS.md`](../AGENTS.md) for the project's stack, module boundaries, and coding standards. +2. Follow the per-topic rules in [`.agent/rules/`](../rules/) and the workflow in [`.agent/workflows/`](../workflows/) matching the task type. +3. Remember the site is a fully static export deployed to GitHub Pages — no server runtime, no API routes. -If any of the above are still template placeholders (marked `TODO`), say so rather than inventing project details. +If a requested change implies something the site can't do statically (a server-side secret, a database, a live API route), say so rather than inventing a workaround. diff --git a/.agent/rules/python.md b/.agent/rules/python.md index 14eb6e5..d2b8655 100644 --- a/.agent/rules/python.md +++ b/.agent/rules/python.md @@ -1,9 +1,8 @@ -# Python Rules +# Python Rules (notebooks/) -- Target Python 3.11+. Use `uv` for dependency management (`uv sync`, `uv add`, `uv run`). +- Target Python 3.11+. Use `uv` for dependency management (`uv sync --extra dev`, `uv add`, `uv run`) inside `notebooks/`. - Format and lint with `ruff` (`ruff format`, `ruff check --fix`). Do not hand-format code that `ruff format` would rewrite. -- Type-check with `mypy`; new modules should be clean under `--strict` where practical. -- Use `pathlib.Path` instead of `os.path`; use `dataclasses` or `pydantic` models instead of loose dicts for structured data. -- Tests live under `python/test/`, mirroring the `python/src/` package layout. Shared fixtures go in `python/test/conftest.py`. -- Prefer dependency injection over module-level singletons so components stay testable. -- Log with the standard `logging` module (never `print`) using module-level loggers (`logging.getLogger(__name__)`). +- Type-check with `mypy` where practical; notebooks are exploratory, so this is advisory, not blocking. +- Strip notebook output before committing (`nbstripout`, wired via `.pre-commit-config.yaml`) — outputs bloat diffs and can leak local file paths. +- Keep reusable logic in plain `.py` modules next to the notebook that uses it; don't duplicate the same analysis code across multiple notebooks. +- `notebooks/` is a standalone workspace, not part of the Next.js build — never import from it in `src/`/`app/`, and don't add it as a build dependency. diff --git a/.agent/rules/typescript_react.md b/.agent/rules/typescript_react.md index b1e4144..665f2cc 100644 --- a/.agent/rules/typescript_react.md +++ b/.agent/rules/typescript_react.md @@ -1,8 +1,9 @@ # TypeScript / React Rules - Target TypeScript 5, `strict: true` in `tsconfig.json`. No `any` without a `// TODO` explaining why. -- Format/lint with `eslint` + `prettier` (or the project's configured equivalents); run `npm run lint` before committing. +- Format/lint with `eslint` (`eslint-config-next`); run `npm run lint` before committing. - Prefer function components with hooks; colocate a component's styles/tests next to the component file. -- State management: local `useState`/`useReducer` first; reach for a store (Zustand/Redux) only when state is shared across distant components. -- Tests live under `typescript/test/`, using Vitest + Testing Library. Snapshot tests are a last resort, not a default. -- Never call browser/Node-only APIs from shared code that also runs in a Tauri/Electron `src-tauri` context without a platform check. +- State management: local `useState`/`useReducer` first; this site has no shared/global store today — don't add one for a single component's state. +- Tests live under `src/components/__tests__/`, using Jest + Testing Library. Snapshot tests are a last resort, not a default. +- Remember this is a static export (`output: 'export'`): no API routes, no server components that need a runtime, no browser-only API calls outside `useEffect`/client components. +- Content is Markdown parsed at build time via `lib/markdown.ts`; new content sections belong under `app/content/
/`, not hardcoded in a component. diff --git a/.agent/skills/build-and-test.md b/.agent/skills/build-and-test.md index 08637a5..fe22677 100644 --- a/.agent/skills/build-and-test.md +++ b/.agent/skills/build-and-test.md @@ -1,12 +1,23 @@ # Skill: Build and Test Everything -Run the full build + test cycle across every language module. +Run the full build + test cycle for the site. ```bash -just lint # runs ruff/eslint/ktlint/clippy/gofmt/clang-format across all modules -just test # runs pytest/vitest/gradle test/cargo test/go test/ctest across all modules -just docs # builds the MkDocs + Sphinx documentation site +npm run lint # eslint +npx tsc --noEmit # type check +npm test # jest unit tests +npm run build # static export to out/ +npx cypress run # e2e, against a running dev/build server +``` + +For the notebooks workspace: + +```bash +cd notebooks +uv sync --extra dev +uv run ruff check . +uv run mypy . # advisory ``` Use this before opening a PR, or whenever asked to "make sure everything still works." -Report which module(s) failed and the first failing assertion/error, not just "tests failed." +Report which step failed and the first failing assertion/error, not just "tests failed." diff --git a/.agent/skills/debug-crash.md b/.agent/skills/debug-crash.md index 3d74395..c363da9 100644 --- a/.agent/skills/debug-crash.md +++ b/.agent/skills/debug-crash.md @@ -1,7 +1,7 @@ -# Skill: Debug a Crash +# Skill: Debug a Build or Runtime Failure -1. Get a reliable repro command and the full crash output (stack trace, exit code, core dump if native code). -2. For native modules (rust/, cpp/), rebuild with debug symbols and reproduce under a debugger (`lldb`/`gdb`) or `rust-gdb`. -3. For managed runtimes (python/, typescript/, kotlin/, java/, go/), capture the full traceback — do not summarize it. -4. Bisect via `git bisect` if the crash is a regression against a known-good commit. -5. Once fixed, add a regression test that would have caught it, and note the root cause in the commit message. +1. Get a reliable repro: the exact command (`npm run dev`/`npm run build`/`npm test`/`npx cypress run`) and the full error output. +2. Capture the full stack trace/browser console output — do not summarize it. +3. For a build-only failure, check first whether it's a static-export constraint (no API routes, no server-only Node APIs in client components, no `window`/`document` access outside `useEffect`). +4. Bisect via `git bisect` if the failure is a regression against a known-good commit. +5. Once fixed, add a regression test (Jest for components/logic, Cypress for a broken user flow) and note the root cause in the commit message. diff --git a/.gitmessage b/.gitmessage new file mode 100644 index 0000000..3f33c08 --- /dev/null +++ b/.gitmessage @@ -0,0 +1,2 @@ + +Co-authored-by: Gemini Code Assist From 833a63c7308c635e0c6484775b991f0a82cc5a7e Mon Sep 17 00:00:00 2001 From: ACFHarbinger Date: Sat, 8 Aug 2026 11:05:28 +0100 Subject: [PATCH 05/27] Remove stray .gitmessage file Not part of the intended cleanup; unrelated content that ended up staged alongside the .agent/ changes. --- .gitmessage | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .gitmessage diff --git a/.gitmessage b/.gitmessage deleted file mode 100644 index 3f33c08..0000000 --- a/.gitmessage +++ /dev/null @@ -1,2 +0,0 @@ - -Co-authored-by: Gemini Code Assist From e53006db702b7abd634da79fd5e63c8123eba5aa Mon Sep 17 00:00:00 2001 From: ACFHarbinger Date: Sat, 8 Aug 2026 11:06:19 +0100 Subject: [PATCH 06/27] Rewrite README with tech badges and local dev/test instructions Replace the plain tech list and course-template boilerplate with a badge-based header (stack + CI/deploy status + license), a project structure overview, and concrete getting-started/testing/deployment instructions, following the style of Image-Toolkit/nglab/WSmart-Route. --- README.md | 152 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 124 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 1e1a4b1..100895a 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,136 @@ -
+
- +# 📓 GitHub Pages — Personal Site & Knowledge Base -# Github Pages +**ACFHarbinger's personal blog and knowledge base: posts, reports, project write-ups, and tool notes, statically exported and deployed to GitHub Pages.** -My own personal [github pages blog](https://acfharbinger.github.io/github-pages/)! +Live Site +CI +Deploy +License: AGPL v3 -
+
- +Next.js +React +TypeScript +Tailwind CSS +Python + +
+ +Jest +Cypress +ESLint +uv +Ruff +Jupyter + + + +--- + +## Overview + +This repository builds [acfharbinger.github.io/github-pages](https://acfharbinger.github.io/github-pages/) — a personal site combining a blog with a small knowledge base: + +| Section | What lives there | +| --- | --- | +| **Posts** | Shorter-form writing | +| **Reports** | Longer, structured write-ups (e.g. audio signal processing, the PCVRP report) | +| **Projects** | Write-ups of things I've built | +| **Tools** | Notes on tools/utilities | +| **Media** | Media-focused content | +| **About / Other** | Everything else | + +It's a fully static [Next.js](https://nextjs.org/) export (`output: 'export'`) — no server, no API routes, no database. Content is authored as Markdown and rendered at build time. ## Tech Stack -- [HTML - HyperText Markup Language](https://html.com/) -- [CSS - Cascading Style Sheets](https://www.w3.org/Style/CSS/Overview.en.html) -- [TypeScript Programming Language](https://www.typescriptlang.org/) -- [JavaScript Programming Language](https://www.javascript.com/) -- [Tailwind CSS Framework](https://tailwindcss.com/) -- [React JavaScript/TypeScript Framework](https://react.dev/) -- [Next.JS React Framework](https://nextjs.org/) -
+- **Framework:** [Next.js](https://nextjs.org/) (App Router) + [React](https://react.dev/) + [TypeScript](https://www.typescriptlang.org/) +- **Styling:** [Tailwind CSS](https://tailwindcss.com/) +- **Content:** Markdown, parsed with [`gray-matter`](https://github.com/jonschlinkert/gray-matter) and [`remark`](https://github.com/remarkjs/remark) +- **Testing:** [Jest](https://jestjs.io/) + [Testing Library](https://testing-library.com/) (unit) · [Cypress](https://www.cypress.io/) (e2e) +- **Linting:** [ESLint](https://eslint.org/) (`eslint-config-next`) +- **Icons:** [lucide-react](https://lucide.dev/) +- **Research workspace:** [Python](https://www.python.org/) 3.11+ managed with [uv](https://github.com/astral-sh/uv), used for the analysis behind some reports + +## Project Structure + +``` +. +├── app/ # Next.js App Router: routes + layouts +│ └── content/ # Markdown content, one folder per section +│ ├── posts/ +│ ├── reports/ +│ ├── projects/ +│ ├── tools/ +│ ├── media/ +│ ├── about/ +│ └── other/ +├── src/ +│ ├── components/ # React components (layout, ui, content wrappers) +│ │ └── __tests__/ # Jest + Testing Library specs +│ └── styles/ +├── lib/ # Markdown loading/parsing helpers +├── cypress/e2e/ # End-to-end specs, one per section +├── notebooks/ # Python/uv workspace for report research +├── docs/research/ # Longer design/research write-ups +└── public/ # Static assets +``` + +## Getting Started + +Prerequisites: [Node.js](https://nodejs.org/) 20+ and npm. + +```bash +git clone https://github.com/ACFHarbinger/github-pages.git +cd github-pages +npm install +npm run dev +``` + +The dev server runs at `http://localhost:3000/github-pages` (the `basePath` matches the GitHub Pages deployment path). + +### Building + +```bash +npm run build # static export to out/ +npm run deploy # build + touch out/.nojekyll +npm start # serve the out/ export locally +``` + +## Testing + +```bash +npm run lint # ESLint +npx tsc --noEmit # type check +npm test # Jest unit tests +npm run test:watch # Jest in watch mode +npx cypress open # Cypress e2e (interactive) +npx cypress run # Cypress e2e (headless) +``` + +Cypress runs against a served build, so run `npm run build && npm start` (or `npm run dev`) in another terminal first — see `cypress.config.js` for the `baseUrl`. + +## Notebooks + +`notebooks/` is a standalone Python workspace (not part of the site build) used to run the analysis behind some reports: + +```bash +cd notebooks +uv sync --extra dev +uv run jupyter lab +``` + +## Deployment + +Pushing to `main` triggers [`.github/workflows/deploy.yml`](.github/workflows/deploy.yml), which builds the static export and publishes `out/` to the `gh-pages` branch via [`peaceiris/actions-gh-pages`](https://github.com/peaceiris/actions-gh-pages). [`.github/workflows/ci.yml`](.github/workflows/ci.yml) runs lint/typecheck/unit/e2e on every push and PR. + +## License - +[AGPL-3.0](LICENSE) --- -Get help: [Post in github's discussion board](https://github.com/orgs/skills/discussions/categories/github-pages) • [Review the GitHub status page](https://www.githubstatus.com/) -
+Get help: [GitHub Discussions](https://github.com/orgs/skills/discussions/categories/github-pages) • [GitHub Status](https://www.githubstatus.com/) From 35588b640882d3f1006707c448fc7a765bf3661b Mon Sep 17 00:00:00 2001 From: ACFHarbinger Date: Sat, 8 Aug 2026 11:33:19 +0100 Subject: [PATCH 07/27] New files from template --- .gitmessage | 2 ++ .../reports/Website Preferences and Advanced Technologies.md} | 0 .../Global Interactive Portfolio Website Architecture.md} | 0 git/messages/chatgpt_coauthor.msg | 2 ++ git/messages/claude_coauthor.msg | 2 ++ git/messages/gemini_coauthor.msg | 2 ++ git/messages/grok_coauthor.msg | 2 ++ 7 files changed, 10 insertions(+) create mode 100644 .gitmessage rename docs/{research/website-preferences-and-advanced-technologies.md => moon/reports/Website Preferences and Advanced Technologies.md} (100%) rename docs/{research/global-interactive-portfolio-website-architecture.md => moon/research/Global Interactive Portfolio Website Architecture.md} (100%) create mode 100644 git/messages/chatgpt_coauthor.msg create mode 100644 git/messages/claude_coauthor.msg create mode 100644 git/messages/gemini_coauthor.msg create mode 100644 git/messages/grok_coauthor.msg diff --git a/.gitmessage b/.gitmessage new file mode 100644 index 0000000..3f33c08 --- /dev/null +++ b/.gitmessage @@ -0,0 +1,2 @@ + +Co-authored-by: Gemini Code Assist diff --git a/docs/research/website-preferences-and-advanced-technologies.md b/docs/moon/reports/Website Preferences and Advanced Technologies.md similarity index 100% rename from docs/research/website-preferences-and-advanced-technologies.md rename to docs/moon/reports/Website Preferences and Advanced Technologies.md diff --git a/docs/research/global-interactive-portfolio-website-architecture.md b/docs/moon/research/Global Interactive Portfolio Website Architecture.md similarity index 100% rename from docs/research/global-interactive-portfolio-website-architecture.md rename to docs/moon/research/Global Interactive Portfolio Website Architecture.md diff --git a/git/messages/chatgpt_coauthor.msg b/git/messages/chatgpt_coauthor.msg new file mode 100644 index 0000000..bfa10fd --- /dev/null +++ b/git/messages/chatgpt_coauthor.msg @@ -0,0 +1,2 @@ + +Co-authored-by: ChatGPT diff --git a/git/messages/claude_coauthor.msg b/git/messages/claude_coauthor.msg new file mode 100644 index 0000000..34548fe --- /dev/null +++ b/git/messages/claude_coauthor.msg @@ -0,0 +1,2 @@ + +Co-Authored-By: Claude diff --git a/git/messages/gemini_coauthor.msg b/git/messages/gemini_coauthor.msg new file mode 100644 index 0000000..3f33c08 --- /dev/null +++ b/git/messages/gemini_coauthor.msg @@ -0,0 +1,2 @@ + +Co-authored-by: Gemini Code Assist diff --git a/git/messages/grok_coauthor.msg b/git/messages/grok_coauthor.msg new file mode 100644 index 0000000..fea2c7e --- /dev/null +++ b/git/messages/grok_coauthor.msg @@ -0,0 +1,2 @@ + +Co-authored-by: Grok From 3c361eaa39ee4d625be73fda899d16119b18505a Mon Sep 17 00:00:00 2001 From: ACFHarbinger Date: Sat, 8 Aug 2026 11:40:54 +0100 Subject: [PATCH 08/27] Adapt CI mirrors and git/ tooling to this repo, remove template framing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restored copies of .forgejo/, .gitea/, .github/, .gitlab/, and git/ still described a polyglot python/typescript/kotlin/java/rust/go/cpp project and referenced the source template by name. Rewritten so every file reflects only this repository: - ci.yml (all three CI hosts) and .gitlab-ci.yml: lint/typecheck/jest/ cypress/build for the Next.js site, ruff for notebooks/ — no cargo/gradle/mvn/cmake/go steps. - security.yml: npm audit + pip-audit only. - benchmark.yml: Lighthouse against the static export, no explanatory commentary about what it replaces. - docs.yml / docs-link-check: a real lychee link-check job over docs/, not a manual no-op with a "restore this later" comment. - agent_sync.yml / agent-sync.yml / the sync stage in .gitlab-ci.yml: same real job on every host — it only ever talks to GitHub's Projects (V2) API regardless of which CI runs it, so the "not portable to Forgejo/Gitea/GitLab" guard was wrong and is gone. - Issue/PR/MR templates: content-area dropdown instead of a language-module dropdown, on every host. - git/CONTRIBUTING.md, git/README.md, git/config/*, git/hooks/*, git/scripts/*: rewritten/restored with no template name, no "ported from " attribution, and a component label set (site/content/notebooks/ci-automation/docs) that matches this repo instead of one label per unused language. None of this repo's directories were removed. A handful of files with zero applicability to this project (per-language rule/roadmap files, the template's own dev-tool roadmap) were deleted outright rather than kept with a "not applicable" banner, per explicit instruction. --- .forgejo/ISSUE_TEMPLATE/bug_report.yml | 49 +++ .forgejo/ISSUE_TEMPLATE/config.yml | 8 + .forgejo/ISSUE_TEMPLATE/feature_request.yml | 35 ++ .forgejo/pull_request_template.md | 26 ++ .forgejo/workflows/agent-sync.yml | 75 ++++ .forgejo/workflows/benchmark.yml | 32 ++ .forgejo/workflows/ci.yml | 69 ++++ .forgejo/workflows/docs.yml | 18 + .forgejo/workflows/security.yml | 28 ++ .gitea/ISSUE_TEMPLATE/bug_report.yml | 49 +++ .gitea/ISSUE_TEMPLATE/config.yml | 8 + .gitea/ISSUE_TEMPLATE/feature_request.yml | 35 ++ .gitea/pull_request_template.md | 26 ++ .gitea/workflows/agent-sync.yml | 75 ++++ .gitea/workflows/benchmark.yml | 32 ++ .gitea/workflows/ci.yml | 69 ++++ .gitea/workflows/docs.yml | 18 + .gitea/workflows/security.yml | 28 ++ .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/agent_sync.yml | 75 ++++ .github/workflows/benchmark.yml | 32 ++ .github/workflows/docs.yml | 18 + .gitlab/.gitlab-ci.yml | 127 ++++++ .gitlab/issue_templates/Bug_Report.md | 26 ++ .gitlab/issue_templates/Feature_Request.md | 17 + .gitlab/merge_request_templates/Default.md | 26 ++ git/CONTRIBUTING.md | 68 ++++ git/README.md | 26 ++ git/codecov.yaml | 15 + git/config/automation_rules.yaml | 61 +++ git/config/project_labels.json | 27 ++ git/hooks/install.sh | 25 ++ git/hooks/post-commit | 39 ++ git/hooks/post-commit.bat | 32 ++ git/hooks/pre-commit | 51 +++ git/scripts/agent_tools.py | 421 ++++++++++++++++++++ git/scripts/check_commit_ref.py | 135 +++++++ git/scripts/sync_backlog.py | 327 +++++++++++++++ 38 files changed, 2229 insertions(+), 1 deletion(-) create mode 100644 .forgejo/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .forgejo/ISSUE_TEMPLATE/config.yml create mode 100644 .forgejo/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .forgejo/pull_request_template.md create mode 100644 .forgejo/workflows/agent-sync.yml create mode 100644 .forgejo/workflows/benchmark.yml create mode 100644 .forgejo/workflows/ci.yml create mode 100644 .forgejo/workflows/docs.yml create mode 100644 .forgejo/workflows/security.yml create mode 100644 .gitea/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .gitea/ISSUE_TEMPLATE/config.yml create mode 100644 .gitea/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .gitea/pull_request_template.md create mode 100644 .gitea/workflows/agent-sync.yml create mode 100644 .gitea/workflows/benchmark.yml create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitea/workflows/docs.yml create mode 100644 .gitea/workflows/security.yml create mode 100644 .github/workflows/agent_sync.yml create mode 100644 .github/workflows/benchmark.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .gitlab/.gitlab-ci.yml create mode 100644 .gitlab/issue_templates/Bug_Report.md create mode 100644 .gitlab/issue_templates/Feature_Request.md create mode 100644 .gitlab/merge_request_templates/Default.md create mode 100644 git/CONTRIBUTING.md create mode 100644 git/README.md create mode 100644 git/codecov.yaml create mode 100644 git/config/automation_rules.yaml create mode 100644 git/config/project_labels.json create mode 100755 git/hooks/install.sh create mode 100755 git/hooks/post-commit create mode 100755 git/hooks/post-commit.bat create mode 100755 git/hooks/pre-commit create mode 100755 git/scripts/agent_tools.py create mode 100755 git/scripts/check_commit_ref.py create mode 100755 git/scripts/sync_backlog.py diff --git a/.forgejo/ISSUE_TEMPLATE/bug_report.yml b/.forgejo/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..ed71da4 --- /dev/null +++ b/.forgejo/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,49 @@ +name: 🐛 Bug Report +description: Report a defect (structured for humans and coding agents) +title: "[Bug]: " +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: | + Thanks for the report. Telling us which page or area is affected speeds up triage. + + - type: dropdown + id: area + attributes: + label: Affected area + description: Which part of the site? + options: + - Posts + - Reports + - Projects + - Tools + - Media + - About / Other + - Notebooks (notebooks/) + - Build / tooling / CI + - Other / unknown + validations: + required: true + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Expected vs actual behavior. + validations: + required: true + + - type: textarea + id: repro + attributes: + label: Steps to reproduce + description: Minimal, exact steps — commands, inputs, environment. + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant log output + render: shell diff --git a/.forgejo/ISSUE_TEMPLATE/config.yml b/.forgejo/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..464e151 --- /dev/null +++ b/.forgejo/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: 🌐 Live Site + url: https://acfharbinger.github.io/github-pages/ + about: The deployed site this repository builds. + - name: 📖 Docs + url: https://github.com/ACFHarbinger/github-pages/tree/main/docs + about: Research notes and write-ups backing the site's reports/posts. diff --git a/.forgejo/ISSUE_TEMPLATE/feature_request.yml b/.forgejo/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..bf3b70e --- /dev/null +++ b/.forgejo/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,35 @@ +name: ✨ Feature Request +description: Propose a new feature or enhancement +title: "[Feature]: " +labels: ["enhancement", "triage"] +body: + - type: textarea + id: problem + attributes: + label: What problem does this solve? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + validations: + required: false + + - type: dropdown + id: area + attributes: + label: Affected area + options: + - Posts + - Reports + - Projects + - Tools + - Media + - About / Other + - Notebooks (notebooks/) + - Build / tooling / CI + - Other / unknown + validations: + required: true diff --git a/.forgejo/pull_request_template.md b/.forgejo/pull_request_template.md new file mode 100644 index 0000000..a68fef2 --- /dev/null +++ b/.forgejo/pull_request_template.md @@ -0,0 +1,26 @@ +# Pull Request + +## Summary + + + +## Affected Area(s) + +- [ ] Site content (`app/content/**`) +- [ ] Components / styling (`src/`) +- [ ] Notebooks / research (`notebooks/`, `docs/moon/research/`, `docs/moon/reports/`) +- [ ] Tooling / CI / docs + +## Type of Change + +- [ ] 🐛 Bug fix +- [ ] ✨ New feature +- [ ] ♻️ Refactor +- [ ] 📚 Content / documentation +- [ ] 🔧 Tooling / CI + +## Verification + +- [ ] `npm run lint` and `npm test` pass. +- [ ] `npm run build` succeeds (static export). +- [ ] Manually checked the affected page(s) with `npm run dev`. diff --git a/.forgejo/workflows/agent-sync.yml b/.forgejo/workflows/agent-sync.yml new file mode 100644 index 0000000..4751974 --- /dev/null +++ b/.forgejo/workflows/agent-sync.yml @@ -0,0 +1,75 @@ +name: Agent Backlog Sync + +# Keeps a GitHub Project (V2) board in sync with docs/moon/ROADMAP.md / +# docs/moon/CHANGELOG.md by orchestrating git/scripts/sync_backlog.py. Runs on +# pushes to main that touch either source-of-truth doc, or on demand via +# workflow_dispatch for maintainers who want to force a resync without a doc +# edit. + +on: + push: + branches: [main] + paths: + - "docs/moon/ROADMAP.md" + - "docs/moon/CHANGELOG.md" + workflow_dispatch: + inputs: + dry_run: + description: "Print the proposed diff without mutating the board" + type: boolean + default: false + +permissions: + contents: read + issues: write + pull-requests: write + +concurrency: + group: agent-backlog-sync + cancel-in-progress: false + +jobs: + sync: + name: Sync backlog to Project board + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + + - name: Install automation dependencies + run: pip install pyyaml requests google-genai + + - name: Validate automation config + run: | + python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" + python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" + + - name: Run backlog sync + env: + GITHUB_TOKEN: ${{ secrets.PROJECT_AUTOMATION_TOKEN }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + PROJECT_ID: ${{ vars.PROJECT_ID }} + run: | + ARGS="--repo-owner ${{ github.repository_owner }} \ + --repo-name $(basename ${{ github.repository }}) \ + --project-id ${PROJECT_ID}" + if [ "${{ inputs.dry_run }}" = "true" ]; then + ARGS="$ARGS --dry-run" + fi + python git/scripts/sync_backlog.py $ARGS + + - name: Summarize run + if: always() + run: | + echo "### Agent Backlog Sync" >> "$GITHUB_STEP_SUMMARY" + echo "Trigger: ${{ github.event_name }}" >> "$GITHUB_STEP_SUMMARY" + echo "Dry run: ${{ inputs.dry_run || 'false' }}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.forgejo/workflows/benchmark.yml b/.forgejo/workflows/benchmark.yml new file mode 100644 index 0000000..8ed5c2a --- /dev/null +++ b/.forgejo/workflows/benchmark.yml @@ -0,0 +1,32 @@ +name: Benchmark + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "app/**" + - "src/**" + - "public/**" + +jobs: + lighthouse: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - name: Serve static export + run: npx serve@latest out -l 3000 & + - name: Wait for server + run: npx wait-on http://localhost:3000/github-pages + - name: Run Lighthouse + run: npx lighthouse http://localhost:3000/github-pages --output=json --output-path=./lighthouse-report.json --chrome-flags="--headless --no-sandbox" + - uses: actions/upload-artifact@v4 + with: + name: lighthouse-report + path: lighthouse-report.json diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..43e9107 --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,69 @@ +name: CI + +on: + workflow_dispatch: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint-typecheck-unit: + name: Lint, typecheck & unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run lint + - run: npx tsc --noEmit + - run: npm test -- --ci --coverage + + build: + name: Build static export + runs-on: ubuntu-latest + needs: lint-typecheck-unit + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + + e2e: + name: Cypress end-to-end + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - name: Run Cypress against the static export + uses: cypress-io/github-action@v6 + with: + start: npm start + wait-on: "http://localhost:3000/github-pages" + + notebooks: + name: Lint notebooks workspace + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + working-directory: notebooks + - run: uv run ruff check . + working-directory: notebooks diff --git a/.forgejo/workflows/docs.yml b/.forgejo/workflows/docs.yml new file mode 100644 index 0000000..7d747fd --- /dev/null +++ b/.forgejo/workflows/docs.yml @@ -0,0 +1,18 @@ +name: Docs + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "docs/**" + +jobs: + link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: lycheeverse/lychee-action@v1 + with: + args: --verbose --no-progress --include-verbatim 'docs/**/*.md' + fail: true diff --git a/.forgejo/workflows/security.yml b/.forgejo/workflows/security.yml new file mode 100644 index 0000000..fcd4bec --- /dev/null +++ b/.forgejo/workflows/security.yml @@ -0,0 +1,28 @@ +name: Security Audit + +on: + workflow_dispatch: + schedule: + - cron: "0 6 * * 1" # every Monday 06:00 UTC + pull_request: + branches: [main] + +jobs: + npm-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm audit --audit-level=high + + pip-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + working-directory: notebooks + - run: uv run pip-audit + working-directory: notebooks diff --git a/.gitea/ISSUE_TEMPLATE/bug_report.yml b/.gitea/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..ed71da4 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,49 @@ +name: 🐛 Bug Report +description: Report a defect (structured for humans and coding agents) +title: "[Bug]: " +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: | + Thanks for the report. Telling us which page or area is affected speeds up triage. + + - type: dropdown + id: area + attributes: + label: Affected area + description: Which part of the site? + options: + - Posts + - Reports + - Projects + - Tools + - Media + - About / Other + - Notebooks (notebooks/) + - Build / tooling / CI + - Other / unknown + validations: + required: true + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Expected vs actual behavior. + validations: + required: true + + - type: textarea + id: repro + attributes: + label: Steps to reproduce + description: Minimal, exact steps — commands, inputs, environment. + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant log output + render: shell diff --git a/.gitea/ISSUE_TEMPLATE/config.yml b/.gitea/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..464e151 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: 🌐 Live Site + url: https://acfharbinger.github.io/github-pages/ + about: The deployed site this repository builds. + - name: 📖 Docs + url: https://github.com/ACFHarbinger/github-pages/tree/main/docs + about: Research notes and write-ups backing the site's reports/posts. diff --git a/.gitea/ISSUE_TEMPLATE/feature_request.yml b/.gitea/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..bf3b70e --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,35 @@ +name: ✨ Feature Request +description: Propose a new feature or enhancement +title: "[Feature]: " +labels: ["enhancement", "triage"] +body: + - type: textarea + id: problem + attributes: + label: What problem does this solve? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + validations: + required: false + + - type: dropdown + id: area + attributes: + label: Affected area + options: + - Posts + - Reports + - Projects + - Tools + - Media + - About / Other + - Notebooks (notebooks/) + - Build / tooling / CI + - Other / unknown + validations: + required: true diff --git a/.gitea/pull_request_template.md b/.gitea/pull_request_template.md new file mode 100644 index 0000000..a68fef2 --- /dev/null +++ b/.gitea/pull_request_template.md @@ -0,0 +1,26 @@ +# Pull Request + +## Summary + + + +## Affected Area(s) + +- [ ] Site content (`app/content/**`) +- [ ] Components / styling (`src/`) +- [ ] Notebooks / research (`notebooks/`, `docs/moon/research/`, `docs/moon/reports/`) +- [ ] Tooling / CI / docs + +## Type of Change + +- [ ] 🐛 Bug fix +- [ ] ✨ New feature +- [ ] ♻️ Refactor +- [ ] 📚 Content / documentation +- [ ] 🔧 Tooling / CI + +## Verification + +- [ ] `npm run lint` and `npm test` pass. +- [ ] `npm run build` succeeds (static export). +- [ ] Manually checked the affected page(s) with `npm run dev`. diff --git a/.gitea/workflows/agent-sync.yml b/.gitea/workflows/agent-sync.yml new file mode 100644 index 0000000..4751974 --- /dev/null +++ b/.gitea/workflows/agent-sync.yml @@ -0,0 +1,75 @@ +name: Agent Backlog Sync + +# Keeps a GitHub Project (V2) board in sync with docs/moon/ROADMAP.md / +# docs/moon/CHANGELOG.md by orchestrating git/scripts/sync_backlog.py. Runs on +# pushes to main that touch either source-of-truth doc, or on demand via +# workflow_dispatch for maintainers who want to force a resync without a doc +# edit. + +on: + push: + branches: [main] + paths: + - "docs/moon/ROADMAP.md" + - "docs/moon/CHANGELOG.md" + workflow_dispatch: + inputs: + dry_run: + description: "Print the proposed diff without mutating the board" + type: boolean + default: false + +permissions: + contents: read + issues: write + pull-requests: write + +concurrency: + group: agent-backlog-sync + cancel-in-progress: false + +jobs: + sync: + name: Sync backlog to Project board + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + + - name: Install automation dependencies + run: pip install pyyaml requests google-genai + + - name: Validate automation config + run: | + python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" + python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" + + - name: Run backlog sync + env: + GITHUB_TOKEN: ${{ secrets.PROJECT_AUTOMATION_TOKEN }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + PROJECT_ID: ${{ vars.PROJECT_ID }} + run: | + ARGS="--repo-owner ${{ github.repository_owner }} \ + --repo-name $(basename ${{ github.repository }}) \ + --project-id ${PROJECT_ID}" + if [ "${{ inputs.dry_run }}" = "true" ]; then + ARGS="$ARGS --dry-run" + fi + python git/scripts/sync_backlog.py $ARGS + + - name: Summarize run + if: always() + run: | + echo "### Agent Backlog Sync" >> "$GITHUB_STEP_SUMMARY" + echo "Trigger: ${{ github.event_name }}" >> "$GITHUB_STEP_SUMMARY" + echo "Dry run: ${{ inputs.dry_run || 'false' }}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.gitea/workflows/benchmark.yml b/.gitea/workflows/benchmark.yml new file mode 100644 index 0000000..8ed5c2a --- /dev/null +++ b/.gitea/workflows/benchmark.yml @@ -0,0 +1,32 @@ +name: Benchmark + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "app/**" + - "src/**" + - "public/**" + +jobs: + lighthouse: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - name: Serve static export + run: npx serve@latest out -l 3000 & + - name: Wait for server + run: npx wait-on http://localhost:3000/github-pages + - name: Run Lighthouse + run: npx lighthouse http://localhost:3000/github-pages --output=json --output-path=./lighthouse-report.json --chrome-flags="--headless --no-sandbox" + - uses: actions/upload-artifact@v4 + with: + name: lighthouse-report + path: lighthouse-report.json diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..43e9107 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,69 @@ +name: CI + +on: + workflow_dispatch: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint-typecheck-unit: + name: Lint, typecheck & unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run lint + - run: npx tsc --noEmit + - run: npm test -- --ci --coverage + + build: + name: Build static export + runs-on: ubuntu-latest + needs: lint-typecheck-unit + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + + e2e: + name: Cypress end-to-end + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - name: Run Cypress against the static export + uses: cypress-io/github-action@v6 + with: + start: npm start + wait-on: "http://localhost:3000/github-pages" + + notebooks: + name: Lint notebooks workspace + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + working-directory: notebooks + - run: uv run ruff check . + working-directory: notebooks diff --git a/.gitea/workflows/docs.yml b/.gitea/workflows/docs.yml new file mode 100644 index 0000000..7d747fd --- /dev/null +++ b/.gitea/workflows/docs.yml @@ -0,0 +1,18 @@ +name: Docs + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "docs/**" + +jobs: + link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: lycheeverse/lychee-action@v1 + with: + args: --verbose --no-progress --include-verbatim 'docs/**/*.md' + fail: true diff --git a/.gitea/workflows/security.yml b/.gitea/workflows/security.yml new file mode 100644 index 0000000..fcd4bec --- /dev/null +++ b/.gitea/workflows/security.yml @@ -0,0 +1,28 @@ +name: Security Audit + +on: + workflow_dispatch: + schedule: + - cron: "0 6 * * 1" # every Monday 06:00 UTC + pull_request: + branches: [main] + +jobs: + npm-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm audit --audit-level=high + + pip-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + working-directory: notebooks + - run: uv run pip-audit + working-directory: notebooks diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2765e73..a68fef2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,7 +8,7 @@ - [ ] Site content (`app/content/**`) - [ ] Components / styling (`src/`) -- [ ] Notebooks / research (`notebooks/`, `docs/research/`) +- [ ] Notebooks / research (`notebooks/`, `docs/moon/research/`, `docs/moon/reports/`) - [ ] Tooling / CI / docs ## Type of Change diff --git a/.github/workflows/agent_sync.yml b/.github/workflows/agent_sync.yml new file mode 100644 index 0000000..4751974 --- /dev/null +++ b/.github/workflows/agent_sync.yml @@ -0,0 +1,75 @@ +name: Agent Backlog Sync + +# Keeps a GitHub Project (V2) board in sync with docs/moon/ROADMAP.md / +# docs/moon/CHANGELOG.md by orchestrating git/scripts/sync_backlog.py. Runs on +# pushes to main that touch either source-of-truth doc, or on demand via +# workflow_dispatch for maintainers who want to force a resync without a doc +# edit. + +on: + push: + branches: [main] + paths: + - "docs/moon/ROADMAP.md" + - "docs/moon/CHANGELOG.md" + workflow_dispatch: + inputs: + dry_run: + description: "Print the proposed diff without mutating the board" + type: boolean + default: false + +permissions: + contents: read + issues: write + pull-requests: write + +concurrency: + group: agent-backlog-sync + cancel-in-progress: false + +jobs: + sync: + name: Sync backlog to Project board + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + + - name: Install automation dependencies + run: pip install pyyaml requests google-genai + + - name: Validate automation config + run: | + python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" + python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" + + - name: Run backlog sync + env: + GITHUB_TOKEN: ${{ secrets.PROJECT_AUTOMATION_TOKEN }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + PROJECT_ID: ${{ vars.PROJECT_ID }} + run: | + ARGS="--repo-owner ${{ github.repository_owner }} \ + --repo-name $(basename ${{ github.repository }}) \ + --project-id ${PROJECT_ID}" + if [ "${{ inputs.dry_run }}" = "true" ]; then + ARGS="$ARGS --dry-run" + fi + python git/scripts/sync_backlog.py $ARGS + + - name: Summarize run + if: always() + run: | + echo "### Agent Backlog Sync" >> "$GITHUB_STEP_SUMMARY" + echo "Trigger: ${{ github.event_name }}" >> "$GITHUB_STEP_SUMMARY" + echo "Dry run: ${{ inputs.dry_run || 'false' }}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000..8ed5c2a --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,32 @@ +name: Benchmark + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "app/**" + - "src/**" + - "public/**" + +jobs: + lighthouse: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - name: Serve static export + run: npx serve@latest out -l 3000 & + - name: Wait for server + run: npx wait-on http://localhost:3000/github-pages + - name: Run Lighthouse + run: npx lighthouse http://localhost:3000/github-pages --output=json --output-path=./lighthouse-report.json --chrome-flags="--headless --no-sandbox" + - uses: actions/upload-artifact@v4 + with: + name: lighthouse-report + path: lighthouse-report.json diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..7d747fd --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,18 @@ +name: Docs + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "docs/**" + +jobs: + link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: lycheeverse/lychee-action@v1 + with: + args: --verbose --no-progress --include-verbatim 'docs/**/*.md' + fail: true diff --git a/.gitlab/.gitlab-ci.yml b/.gitlab/.gitlab-ci.yml new file mode 100644 index 0000000..6584500 --- /dev/null +++ b/.gitlab/.gitlab-ci.yml @@ -0,0 +1,127 @@ +stages: + - test + - security + - docs + - benchmark + - sync + +# ========================================== +# STAGE: TEST (ci.yml) +# ========================================== +lint-typecheck-unit: + stage: test + image: node:20 + script: + - npm ci + - npm run lint + - npx tsc --noEmit + - npm test -- --ci --coverage + +build: + stage: test + image: node:20 + needs: ["lint-typecheck-unit"] + script: + - npm ci + - npm run build + artifacts: + paths: + - out + +e2e: + stage: test + image: cypress/browsers:node-20.11.1-chrome-123.0.6312.86-1-ff-124.0-edge-122.0.2365.92-1 + needs: ["build"] + script: + - npm ci + - npm run build + - npx start-server-and-test start http://localhost:3000/github-pages "npx cypress run" + +lint-notebooks: + stage: test + image: python:3.12 + script: + - pip install uv + - cd notebooks + - uv sync --extra dev + - uv run ruff check . + +# ========================================== +# STAGE: SECURITY (security.yml) +# ========================================== +security-npm-audit: + stage: security + image: node:20 + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule"' + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + script: + - npm audit --audit-level=high + +security-pip-audit: + stage: security + image: python:3.12 + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule"' + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + script: + - pip install uv + - cd notebooks + - uv sync --extra dev + - uv run pip-audit + +# ========================================== +# STAGE: DOCS (docs.yml) +# ========================================== +docs-link-check: + stage: docs + image: lycheeverse/lychee:latest + rules: + - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' + changes: + - "docs/**/*" + script: + - lychee --verbose --no-progress --include-verbatim 'docs/**/*.md' + +# ========================================== +# STAGE: BENCHMARK (benchmark.yml) +# ========================================== +lighthouse: + stage: benchmark + image: node:20 + rules: + - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH" + changes: + - "app/**/*" + - "src/**/*" + - "public/**/*" + - if: '$CI_PIPELINE_SOURCE == "web"' + script: + - npm ci + - npm run build + - npx serve@latest out -l 3000 & + - npx wait-on http://localhost:3000/github-pages + - npx lighthouse http://localhost:3000/github-pages --output=json --output-path=./lighthouse-report.json --chrome-flags="--headless --no-sandbox" + artifacts: + paths: + - lighthouse-report.json + +# ========================================== +# STAGE: SYNC (agent_sync.yml) +# ========================================== +agent-backlog-sync: + stage: sync + image: python:3.12 + rules: + - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' + changes: + - "docs/moon/ROADMAP.md" + - "docs/moon/CHANGELOG.md" + - if: '$CI_PIPELINE_SOURCE == "web"' + script: + - pip install pyyaml requests google-genai + - python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" + - python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" + - python git/scripts/sync_backlog.py --repo-owner ACFHarbinger --repo-name github-pages --project-id "$PROJECT_ID" + variables: + GITHUB_TOKEN: $PROJECT_AUTOMATION_TOKEN diff --git a/.gitlab/issue_templates/Bug_Report.md b/.gitlab/issue_templates/Bug_Report.md new file mode 100644 index 0000000..2781491 --- /dev/null +++ b/.gitlab/issue_templates/Bug_Report.md @@ -0,0 +1,26 @@ + + +### Affected area + +- [ ] Posts +- [ ] Reports +- [ ] Projects +- [ ] Tools +- [ ] Media +- [ ] About / Other +- [ ] Notebooks (`notebooks/`) +- [ ] Build / tooling / CI +- [ ] Other / unknown + +### What happened? + + +### Steps to reproduce + + +### Relevant log output +```shell + +``` + +/label ~"bug" ~"triage" diff --git a/.gitlab/issue_templates/Feature_Request.md b/.gitlab/issue_templates/Feature_Request.md new file mode 100644 index 0000000..80205ae --- /dev/null +++ b/.gitlab/issue_templates/Feature_Request.md @@ -0,0 +1,17 @@ +### What problem does this solve? + +### Proposed solution + +### Affected area + +- [ ] Posts +- [ ] Reports +- [ ] Projects +- [ ] Tools +- [ ] Media +- [ ] About / Other +- [ ] Notebooks (`notebooks/`) +- [ ] Build / tooling / CI +- [ ] Other / unknown + +/label ~"enhancement" ~"triage" diff --git a/.gitlab/merge_request_templates/Default.md b/.gitlab/merge_request_templates/Default.md new file mode 100644 index 0000000..a68fef2 --- /dev/null +++ b/.gitlab/merge_request_templates/Default.md @@ -0,0 +1,26 @@ +# Pull Request + +## Summary + + + +## Affected Area(s) + +- [ ] Site content (`app/content/**`) +- [ ] Components / styling (`src/`) +- [ ] Notebooks / research (`notebooks/`, `docs/moon/research/`, `docs/moon/reports/`) +- [ ] Tooling / CI / docs + +## Type of Change + +- [ ] 🐛 Bug fix +- [ ] ✨ New feature +- [ ] ♻️ Refactor +- [ ] 📚 Content / documentation +- [ ] 🔧 Tooling / CI + +## Verification + +- [ ] `npm run lint` and `npm test` pass. +- [ ] `npm run build` succeeds (static export). +- [ ] Manually checked the affected page(s) with `npm run dev`. diff --git a/git/CONTRIBUTING.md b/git/CONTRIBUTING.md new file mode 100644 index 0000000..f74def7 --- /dev/null +++ b/git/CONTRIBUTING.md @@ -0,0 +1,68 @@ +# Contributing to github-pages + +[![Next.js](https://img.shields.io/badge/Next.js-14-000000?logo=nextdotjs&logoColor=white)](https://nextjs.org/) +[![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) +[![Python](https://img.shields.io/badge/Python-3.11+-3776ab?logo=python&logoColor=white)](https://www.python.org/) +[![CI](https://github.com/ACFHarbinger/github-pages/actions/workflows/ci.yml/badge.svg)](https://github.com/ACFHarbinger/github-pages/actions/workflows/ci.yml) + +Thank you for your interest in contributing! This document covers setup, style, and the PR process for this repository. + +--- + +## Table of Contents + +1. [Getting Started](#1-getting-started) +2. [Development Setup](#2-development-setup) +3. [Code Style Guidelines](#3-code-style-guidelines) +4. [Git Workflow](#4-git-workflow) +5. [Pull Request Process](#5-pull-request-process) +6. [Testing Requirements](#6-testing-requirements) +7. [Issue Reporting](#7-issue-reporting) + +--- + +## 1. Getting Started + +### 1.1 Prerequisites + +- Git, [Node.js](https://nodejs.org/) >= 20, `npm`. +- For `notebooks/`: `python` (>= 3.11) + [`uv`](https://github.com/astral-sh/uv). +- `pre-commit` (`pip install pre-commit && pre-commit install`). + +### 1.2 Clone and bootstrap + +```bash +git clone https://github.com/ACFHarbinger/github-pages.git +cd github-pages +npm install +npm run dev +``` + +## 2. Development Setup + +The site lives at the repo root (`app/`, `src/`, `lib/`) as a single Next.js app; `notebooks/` is a separate `uv`-managed Python workspace (`cd notebooks && uv sync --extra dev`). + +## 3. Code Style Guidelines + +Follow the rules in [`.agent/rules/`](../.agent/rules/), primarily [`typescript_react.md`](../.agent/rules/typescript_react.md) and [`python.md`](../.agent/rules/python.md). Linting/formatting runs automatically via `.pre-commit-config.yaml` — run `pre-commit run --all-files` before pushing. + +## 4. Git Workflow + +- Branch from `main`: `feature/` or `fix/`. +- Keep commits focused; write commit messages that explain *why*, not just *what*. +- Rebase onto `main` before opening a PR. + +## 5. Pull Request Process + +1. Fill out the [PR template](../.github/PULL_REQUEST_TEMPLATE.md) in full. +2. Ensure CI is green (`npm run lint && npm test && npm run build`). +3. Request review; address feedback with new commits (don't force-push during review). +4. Squash-merge once approved. + +## 6. Testing Requirements + +New components/logic get a Jest test; new user-facing flows get a Cypress spec. See [`.agent/rules/test_writing.md`](../.agent/rules/test_writing.md). + +## 7. Issue Reporting + +Use the issue templates under [`.github/ISSUE_TEMPLATE/`](../.github/ISSUE_TEMPLATE/) — they help both humans and coding agents triage faster. diff --git a/git/README.md b/git/README.md new file mode 100644 index 0000000..14c10b3 --- /dev/null +++ b/git/README.md @@ -0,0 +1,26 @@ +# git/ + +Local git automation for this repository. + +| Directory | Purpose | +| --- | --- | +| `config/` | `automation_rules.yaml` (policy for the backlog sync agent) and `project_labels.json` (label taxonomy) | +| `scripts/` | `agent_tools.py` (ProjectV2 GraphQL client), `sync_backlog.py` (roadmap→board reconciler), `check_commit_ref.py` (commit-message ticket linker) | +| `hooks/` | Local git hooks (`pre-commit`, `post-commit`) plus `install.sh` to symlink them into `.git/hooks/` | + +## Setup + +```bash +bash git/hooks/install.sh +export PROJECT_ID="PVT_..." # ProjectV2 node ID, see `gh project view --owner --format json` +export GITHUB_TOKEN="..." # GitHub token with repo + project scopes +export GEMINI_API_KEY="..." # Google Gemini API key with project + roadmap scopes +``` + +## CI + +`.github/workflows/agent_sync.yml` runs `git/scripts/sync_backlog.py` on +every push to `docs/moon/ROADMAP.md` or `docs/moon/CHANGELOG.md`, or on demand via +`workflow_dispatch`. It needs two repository secrets +(`PROJECT_AUTOMATION_TOKEN`, `GEMINI_API_KEY`) and one repository variable +(`PROJECT_ID`) configured before it can mutate a live board. diff --git a/git/codecov.yaml b/git/codecov.yaml new file mode 100644 index 0000000..d453edf --- /dev/null +++ b/git/codecov.yaml @@ -0,0 +1,15 @@ +coverage: + status: + project: + default: + # set target (e.g. 60%) to fail the build if coverage is too low + target: 60% + patch: + default: + # basic just to show current patch + target: 0% + threshold: 0% + base: auto + +# temporarily avoid comments to PRs and so on +comment: false diff --git a/git/config/automation_rules.yaml b/git/config/automation_rules.yaml new file mode 100644 index 0000000..92fc0aa --- /dev/null +++ b/git/config/automation_rules.yaml @@ -0,0 +1,61 @@ +# automation_rules.yaml +# +# Behavioral configuration for the sync_backlog / agent_tools automation +# suite. This file is read by git/scripts/sync_backlog.py at runtime and +# is NOT interpreted by GitHub itself -- it is our own DSL for how the +# LLM-driven backlog agent should behave, particularly under ambiguity or +# conflict. Keep this human-editable; the agent treats it as policy, not code. + +version: 1 + +project: + # GitHub Project (V2) number as shown in the project URL, e.g. + # https://github.com/users//projects/ + number: 16 + owner: "ACFHarbinger" + owner_type: "user" # "user" | "org" + +sync: + # Files the agent is allowed to read as its source of truth for tickets. + source_documents: + - "docs/moon/ROADMAP.md" + - "docs/moon/CHANGELOG.md" + # Directional policy: what happens when the doc and the board disagree. + conflict_resolution: + # "doc_wins": overwrite board state from markdown + # "board_wins": never downgrade a board card from markdown re-parse + # "flag_only": never mutate, just label agent:needs-human + strategy: "flag_only" + escalation_label: "agent:needs-human" + max_auto_mutations_per_run: 15 # circuit breaker: refuse to bulk-close/create beyond this + # Statuses the agent is permitted to move a ticket into without human sign-off. + auto_transition_allowed: + - "status:backlog" + - "status:ready" + - "status:in-progress" + # Statuses that always require a human to move (agent may only *suggest*). + human_confirm_required: + - "status:done" + - "status:in-review" + +llm: + provider: "google-genai" + model: "gemini-2.5-pro" + temperature: 0.1 + max_output_tokens: 4096 + # Refuse to act on LLM output that isn't valid structured JSON matching + # the expected ticket schema -- never eval free text into a mutation. + require_structured_output: true + +commit_ref: + # Regex family accepted by check_commit_ref.py, e.g. "Refs: #123", "Fixes DRT-42" + patterns: + - '(?i)\b(fixes|closes|refs|relates-to)\s*:?\s*#(\d+)\b' + - '(?i)\bDRT-(\d+)\b' + # If no ticket reference is found on a commit touching src/, warn but do not block. + block_commit_on_missing_ref: false + +notifications: + # Where agent:needs-human escalations get an extra nudge, beyond the label itself. + comment_on_issue: true + mention_users: [] # e.g. ["ACFHarbinger"] diff --git a/git/config/project_labels.json b/git/config/project_labels.json new file mode 100644 index 0000000..a5dad34 --- /dev/null +++ b/git/config/project_labels.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema#", + "description": "Canonical label taxonomy for github-pages. Consumed by sync_backlog.py to validate/create labels and by agent_tools.py to route tickets to Project V2 fields.", + "components": [ + { "name": "component:site", "color": "3178C6", "description": "app/, src/, lib/ — the Next.js site" }, + { "name": "component:content", "color": "0E8A16", "description": "app/content/ — posts, reports, projects, tools, media, about, other" }, + { "name": "component:notebooks", "color": "3776AB", "description": "notebooks/ Python workspace" }, + { "name": "component:ci-automation","color": "C5DEF5", "description": "GitHub Actions, bots, and repo automation" }, + { "name": "component:docs", "color": "BFD4F2", "description": "Documentation and ADRs" } + ], + "priority": [ + { "name": "priority:p0", "color": "B60205", "description": "Drop everything; blocks release or CI" }, + { "name": "priority:p1", "color": "D93F0B", "description": "Should land in the current sprint" }, + { "name": "priority:p2", "color": "FBCA04", "description": "Planned, not urgent" } + ], + "status": [ + { "name": "status:backlog", "color": "EDEDED", "description": "Not yet assessed by agent or maintainer" }, + { "name": "status:ready", "color": "0E8A16", "description": "Scoped and ready for work" }, + { "name": "status:in-progress", "color": "1D76DB", "description": "Actively being worked on" }, + { "name": "status:in-review", "color": "5319E7", "description": "PR open, awaiting review" }, + { "name": "status:done", "color": "0E8A16", "description": "Merged and verified" } + ], + "agent": [ + { "name": "agent:generated", "color": "E4E669", "description": "Ticket authored by sync_backlog.py from roadmap/changelog" }, + { "name": "agent:needs-human", "color": "D93F0B", "description": "Agent flagged ambiguity requiring maintainer input" } + ] +} diff --git a/git/hooks/install.sh b/git/hooks/install.sh new file mode 100755 index 0000000..47bb938 --- /dev/null +++ b/git/hooks/install.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# +# install.sh -- symlink git/hooks/* into .git/hooks/ so they actually run. +# +# `.git/hooks/` is local-only and untracked by design (git will not let a +# repo ship hooks that execute automatically on clone, for security +# reasons). This script is the one-time, explicit opt-in step a contributor +# runs to wire our tracked hooks into their local git. +# +# Usage: +# bash git/hooks/install.sh + +set -euo pipefail + +REPO_ROOT="$(git rev-parse --show-toplevel)" +HOOKS_SRC="${REPO_ROOT}/git/hooks" +HOOKS_DST="${REPO_ROOT}/.git/hooks" + +for hook in pre-commit post-commit; do + ln -sf "../../git/hooks/${hook}" "${HOOKS_DST}/${hook}" + chmod +x "${HOOKS_SRC}/${hook}" + echo "Linked ${hook} -> .git/hooks/${hook}" +done + +echo "Done. Set PROJECT_ID and GITHUB_TOKEN in your shell env to enable live board updates from post-commit." diff --git a/git/hooks/post-commit b/git/hooks/post-commit new file mode 100755 index 0000000..f6c7cd2 --- /dev/null +++ b/git/hooks/post-commit @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# +# post-commit hook: verify the just-made commit references a ticket ID and, +# if so, best-effort transition that ticket's board status. +# +# This file is NOT executed automatically by git -- git only runs hooks +# from `.git/hooks/`, which is untracked. Run git/hooks/install.sh to +# symlink this in. +# +# Fails open: a non-zero exit here would abort nothing (post-commit cannot +# block a commit that already happened), but we still exit 0 explicitly so +# tooling that inspects hook exit codes doesn't misreport a failure. + +set -uo pipefail + +REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" +if [[ -z "${REPO_ROOT}" ]]; then + exit 0 +fi + +PYTHON_BIN="${PYTHON_BIN:-python3}" +CHECK_SCRIPT="${REPO_ROOT}/git/scripts/check_commit_ref.py" +COMMIT_MSG_FILE="${REPO_ROOT}/.git/COMMIT_EDITMSG" + +if [[ ! -f "${CHECK_SCRIPT}" ]]; then + echo "post-commit: check_commit_ref.py not found, skipping" >&2 + exit 0 +fi + +if ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then + echo "post-commit: ${PYTHON_BIN} not found on PATH, skipping ticket check" >&2 + exit 0 +fi + +"${PYTHON_BIN}" "${CHECK_SCRIPT}" --commit-msg-file "${COMMIT_MSG_FILE}" || { + echo "post-commit: ticket reference check reported an issue (non-blocking)" >&2 +} + +exit 0 diff --git a/git/hooks/post-commit.bat b/git/hooks/post-commit.bat new file mode 100755 index 0000000..bdfc6b6 --- /dev/null +++ b/git/hooks/post-commit.bat @@ -0,0 +1,32 @@ +@echo off +REM post-commit.bat -- Windows equivalent of git/hooks/post-commit. +REM Verifies the just-made commit references a ticket ID and, if so, +REM best-effort transitions that ticket's board status. Fails open: always +REM exits 0, since post-commit cannot un-make a commit that already happened. + +setlocal + +for /f "delims=" %%R in ('git rev-parse --show-toplevel 2^>nul') do set REPO_ROOT=%%R +if "%REPO_ROOT%"=="" exit /b 0 + +set PYTHON_BIN=python +set CHECK_SCRIPT=%REPO_ROOT%\git\scripts\check_commit_ref.py +set COMMIT_MSG_FILE=%REPO_ROOT%\.git\COMMIT_EDITMSG + +if not exist "%CHECK_SCRIPT%" ( + echo post-commit: check_commit_ref.py not found, skipping 1>&2 + exit /b 0 +) + +where %PYTHON_BIN% >nul 2>nul +if errorlevel 1 ( + echo post-commit: python not found on PATH, skipping ticket check 1>&2 + exit /b 0 +) + +%PYTHON_BIN% "%CHECK_SCRIPT%" --commit-msg-file "%COMMIT_MSG_FILE%" +if errorlevel 1 ( + echo post-commit: ticket reference check reported an issue ^(non-blocking^) 1>&2 +) + +exit /b 0 diff --git a/git/hooks/pre-commit b/git/hooks/pre-commit new file mode 100755 index 0000000..f987b03 --- /dev/null +++ b/git/hooks/pre-commit @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# +# pre-commit hook: soft-warn (never block, by default) when the *staged* +# commit message draft is unavailable to check yet -- ticket-reference +# enforcement itself happens in commit-msg / post-commit, since the commit +# message doesn't exist at pre-commit time. This hook's job is limited to +# fast, local sanity checks that should run before we even open an editor. +# +# Currently: verifies git/config/automation_rules.yaml and +# git/config/project_labels.json are valid, since a broken config would +# otherwise silently disable all downstream automation. + +set -uo pipefail + +REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" +if [[ -z "${REPO_ROOT}" ]]; then + exit 0 +fi + +PYTHON_BIN="${PYTHON_BIN:-python3}" + +if ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then + exit 0 +fi + +"${PYTHON_BIN}" - "${REPO_ROOT}" <<'PYEOF' +import json +import sys +from pathlib import Path + +root = Path(sys.argv[1]) +labels_path = root / "git" / "config" / "project_labels.json" +rules_path = root / "git" / "config" / "automation_rules.yaml" + +try: + if labels_path.exists(): + json.loads(labels_path.read_text(encoding="utf-8")) +except json.JSONDecodeError as exc: + print(f"pre-commit: invalid project_labels.json: {exc}", file=sys.stderr) + sys.exit(1) + +try: + import yaml + if rules_path.exists(): + yaml.safe_load(rules_path.read_text(encoding="utf-8")) +except Exception as exc: # noqa: BLE001 + print(f"pre-commit: invalid automation_rules.yaml: {exc}", file=sys.stderr) + sys.exit(1) +PYEOF + +exit $? diff --git a/git/scripts/agent_tools.py b/git/scripts/agent_tools.py new file mode 100755 index 0000000..ce40dce --- /dev/null +++ b/git/scripts/agent_tools.py @@ -0,0 +1,421 @@ +"""Callable tool library for LLM-based repository agents. + +This module exposes a small set of pure(ish) functions intended to be +registered as function-calling / MCP tools for an LLM agent that manages +this repository's GitHub Project (V2) board. Each function: + +- Has a fully type-hinted signature and a docstring in Google style, so it + can be introspected automatically for tool schemas (e.g. via + ``inspect.signature`` + docstring parsing) or fed to `google-genai`'s + automatic function calling. +- Talks to GitHub exclusively through the GraphQL ``ProjectV2`` API via + :class:`GitHubProjectClient`, never the older REST Projects API (deprecated). +- Is side-effect-isolated: network calls live behind ``GitHubProjectClient`` + so this module can be unit-tested with a fake client. + +Environment: + GITHUB_TOKEN: A token with ``repo`` and ``project`` scopes. + GITHUB_REPOSITORY: ``owner/name``, injected automatically in Actions. +""" + +from __future__ import annotations + +import json +import os +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Literal + +import requests + +GRAPHQL_ENDPOINT = "https://api.github.com/graphql" +CONFIG_DIR = Path(__file__).resolve().parent.parent / "config" + +TicketStatus = Literal[ + "status:backlog", + "status:ready", + "status:in-progress", + "status:in-review", + "status:done", +] + + +class GitHubAPIError(RuntimeError): + """Raised when the GitHub GraphQL API returns an error payload.""" + + +@dataclass(frozen=True) +class GitHubProjectClient: + """Thin GraphQL client scoped to ProjectV2 mutations/queries. + + Attributes: + token: GitHub personal access token or Actions ``GITHUB_TOKEN``. + session: Injectable ``requests.Session`` for testability. + """ + + token: str + session: requests.Session | None = None + + def _session(self) -> requests.Session: + return self.session or requests.Session() + + def execute(self, query: str, variables: dict[str, Any]) -> dict[str, Any]: + """Execute a GraphQL query/mutation against the GitHub API. + + Args: + query: A GraphQL document string. + variables: GraphQL variables for the document. + + Returns: + The ``data`` object from the GraphQL response. + + Raises: + GitHubAPIError: If the response contains an ``errors`` array + or a non-2xx HTTP status. + """ + response = self._session().post( + GRAPHQL_ENDPOINT, + json={"query": query, "variables": variables}, + headers={ + "Authorization": f"Bearer {self.token}", + "Content-Type": "application/json", + }, + timeout=30, + ) + payload = response.json() + if response.status_code >= 300 or "errors" in payload: + raise GitHubAPIError(json.dumps(payload.get("errors", payload))) + return payload["data"] + + +def _load_json_config(filename: str) -> dict[str, Any]: + """Load a JSON config file from ``git/config``. + + Args: + filename: Basename of the config file, e.g. ``"project_labels.json"``. + + Returns: + Parsed JSON content. + """ + path = CONFIG_DIR / filename + return json.loads(path.read_text(encoding="utf-8")) + + +def get_client() -> GitHubProjectClient: + """Build a :class:`GitHubProjectClient` from the ``GITHUB_TOKEN`` env var. + + Returns: + A configured client instance. + + Raises: + RuntimeError: If ``GITHUB_TOKEN`` is not set. + """ + token = os.environ.get("GITHUB_TOKEN") + if not token: + raise RuntimeError("GITHUB_TOKEN is not set in the environment") + return GitHubProjectClient(token=token) + + +def initialize_ticket( + repo_owner: str, + repo_name: str, + title: str, + body: str, + component: str, + priority: str, + project_id: str, + client: GitHubProjectClient | None = None, +) -> dict[str, Any]: + """Create a GitHub issue and add it to the ProjectV2 board. + + This is the canonical entry point an agent should call when the backlog + document describes new, unscoped work. It creates the issue, attaches + the component/priority labels from ``project_labels.json``, then adds + the resulting node to the target project. + + Args: + repo_owner: GitHub org or user that owns the repository. + repo_name: Repository name. + title: Issue title, ideally mirroring the roadmap bullet text. + body: Markdown issue body (should include a "Source" link back to + the roadmap/changelog line it was derived from). + component: One of the ``component:*`` labels from + ``project_labels.json``. + priority: One of the ``priority:*`` labels from + ``project_labels.json``. + project_id: The ``ProjectV2`` node ID (not the human-facing number). + client: Optional injected client for testing. + + Returns: + A dict with ``issue_id``, ``issue_number``, and ``project_item_id``. + """ + client = client or get_client() + labels = _validated_labels(component, priority) + + repo_query = """ + query($owner: String!, $name: String!) { + repository(owner: $owner, name: $name) { id } + } + """ + repo_data = client.execute(repo_query, {"owner": repo_owner, "name": repo_name}) + repository_id = repo_data["repository"]["id"] + + create_mutation = """ + mutation($repositoryId: ID!, $title: String!, $body: String!, $labelIds: [ID!]) { + createIssue(input: { + repositoryId: $repositoryId, title: $title, body: $body, labelIds: $labelIds + }) { + issue { id number } + } + } + """ + label_ids = _resolve_label_ids(client, repo_owner, repo_name, labels) + created = client.execute( + create_mutation, + { + "repositoryId": repository_id, + "title": title, + "body": body, + "labelIds": label_ids, + }, + ) + issue = created["createIssue"]["issue"] + + add_to_project_mutation = """ + mutation($projectId: ID!, $contentId: ID!) { + addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) { + item { id } + } + } + """ + project_data = client.execute( + add_to_project_mutation, {"projectId": project_id, "contentId": issue["id"]} + ) + + return { + "issue_id": issue["id"], + "issue_number": issue["number"], + "project_item_id": project_data["addProjectV2ItemById"]["item"]["id"], + } + + +def transition_ticket( + project_id: str, + item_id: str, + status_field_id: str, + status_option_id: str, + new_status: TicketStatus, + client: GitHubProjectClient | None = None, +) -> dict[str, Any]: + """Move a project item to a new single-select status option. + + Callers are expected to have already resolved ``status_field_id`` and + ``status_option_id`` via :func:`resolve_status_field` -- this function + performs the mutation only, keeping it a minimal, auditable unit that + an LLM agent can invoke repeatedly without re-deriving IDs. + + Args: + project_id: ``ProjectV2`` node ID. + item_id: ``ProjectV2Item`` node ID for the target ticket. + status_field_id: Node ID of the "Status" single-select field. + status_option_id: Node ID of the target option (e.g. "In Progress"). + new_status: Human-readable status label, used only for the return + payload / logging -- not sent to the API. + client: Optional injected client for testing. + + Returns: + A dict confirming the applied ``new_status`` and updated item id. + """ + client = client or get_client() + mutation = """ + mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { + updateProjectV2ItemFieldValue(input: { + projectId: $projectId, + itemId: $itemId, + fieldId: $fieldId, + value: { singleSelectOptionId: $optionId } + }) { + projectV2Item { id } + } + } + """ + data = client.execute( + mutation, + { + "projectId": project_id, + "itemId": item_id, + "fieldId": status_field_id, + "optionId": status_option_id, + }, + ) + return { + "new_status": new_status, + "project_item_id": data["updateProjectV2ItemFieldValue"]["projectV2Item"]["id"], + } + + +def close_ticket( + issue_id: str, + reason: Literal["COMPLETED", "NOT_PLANNED"] = "COMPLETED", + client: GitHubProjectClient | None = None, +) -> dict[str, Any]: + """Close a GitHub issue. + + Args: + issue_id: The issue's GraphQL node ID. + reason: GitHub's closure reason enum. + client: Optional injected client for testing. + + Returns: + A dict with the closed issue's ``state``. + """ + client = client or get_client() + mutation = """ + mutation($issueId: ID!, $reason: IssueClosedStateReason!) { + closeIssue(input: { issueId: $issueId, stateReason: $reason }) { + issue { state } + } + } + """ + data = client.execute(mutation, {"issueId": issue_id, "reason": reason}) + return {"state": data["closeIssue"]["issue"]["state"]} + + +def resolve_status_field( + project_id: str, client: GitHubProjectClient | None = None +) -> dict[str, Any]: + """Fetch the "Status" single-select field and its option IDs for a project. + + Args: + project_id: ``ProjectV2`` node ID. + client: Optional injected client for testing. + + Returns: + A dict mapping option name -> option node ID, plus the field's own + node ID under the ``"__field_id__"`` key. + """ + client = client or get_client() + query = """ + query($projectId: ID!) { + node(id: $projectId) { + ... on ProjectV2 { + fields(first: 50) { + nodes { + ... on ProjectV2SingleSelectField { + id + name + options { id name } + } + } + } + } + } + } + """ + data = client.execute(query, {"projectId": project_id}) + for field in data["node"]["fields"]["nodes"]: + if field and field.get("name") == "Status": + result = {opt["name"]: opt["id"] for opt in field["options"]} + result["__field_id__"] = field["id"] + return result + raise GitHubAPIError("No 'Status' single-select field found on project") + + +def _validated_labels(component: str, priority: str) -> list[str]: + """Validate a component/priority pair against ``project_labels.json``. + + Args: + component: A ``component:*`` label name. + priority: A ``priority:*`` label name. + + Returns: + ``[component, priority]`` if both are recognized. + + Raises: + ValueError: If either label is not present in the taxonomy. + """ + taxonomy = _load_json_config("project_labels.json") + known_components = {item["name"] for item in taxonomy["components"]} + known_priorities = {item["name"] for item in taxonomy["priority"]} + if component not in known_components: + raise ValueError(f"Unknown component label: {component}") + if priority not in known_priorities: + raise ValueError(f"Unknown priority label: {priority}") + return [component, priority] + + +def _resolve_label_ids( + client: GitHubProjectClient, owner: str, name: str, label_names: list[str] +) -> list[str]: + """Resolve label name strings to GraphQL node IDs, creating missing ones. + + Args: + client: Active GraphQL client. + owner: Repository owner. + name: Repository name. + label_names: Label names to resolve. + + Returns: + List of label node IDs in the same order as ``label_names``. + """ + query = """ + query($owner: String!, $name: String!, $labelName: String!) { + repository(owner: $owner, name: $name) { + label(name: $labelName) { id } + id + } + } + """ + label_ids: list[str] = [] + for label_name in label_names: + data = client.execute( + query, {"owner": owner, "name": name, "labelName": label_name} + ) + existing = data["repository"]["label"] + if existing: + label_ids.append(existing["id"]) + continue + label_ids.append( + _create_label(client, data["repository"]["id"], label_name) + ) + return label_ids + + +def _create_label(client: GitHubProjectClient, repository_id: str, label_name: str) -> str: + """Create a repository label from the taxonomy's configured color. + + Args: + client: Active GraphQL client. + repository_id: Repository node ID. + label_name: Label name to create, must exist in ``project_labels.json``. + + Returns: + The newly created label's node ID. + """ + taxonomy = _load_json_config("project_labels.json") + all_labels = [ + entry + for group in ("components", "priority", "status", "agent") + for entry in taxonomy[group] + ] + match = next(item for item in all_labels if item["name"] == label_name) + + mutation = """ + mutation($repositoryId: ID!, $name: String!, $color: String!, $description: String!) { + createLabel(input: { + repositoryId: $repositoryId, name: $name, color: $color, description: $description + }) { + label { id } + } + } + """ + data = client.execute( + mutation, + { + "repositoryId": repository_id, + "name": match["name"], + "color": match["color"], + "description": match["description"], + }, + ) + return data["createLabel"]["label"]["id"] diff --git a/git/scripts/check_commit_ref.py b/git/scripts/check_commit_ref.py new file mode 100755 index 0000000..02b93a0 --- /dev/null +++ b/git/scripts/check_commit_ref.py @@ -0,0 +1,135 @@ +"""Commit-message ticket reference checker and board updater. + +Invoked by ``git/hooks/post-commit`` (see that file for the wiring +strategy). Reads the just-made commit message, checks it against the regex +patterns configured in ``automation_rules.yaml``, and -- if a ticket +reference is found -- transitions that issue to ``status:in-progress`` on +the ProjectV2 board via :mod:`agent_tools`. + +This script is deliberately fail-open: any network/API error is logged and +swallowed so that a flaky GitHub API call never blocks a local commit. + +Usage: + python git/scripts/check_commit_ref.py --commit-msg-file .git/COMMIT_EDITMSG +""" + +from __future__ import annotations + +import argparse +import logging +import os +import re +import sys +from pathlib import Path + +import yaml + +import agent_tools + +logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") +logger = logging.getLogger("check_commit_ref") + +REPO_ROOT = Path(__file__).resolve().parent.parent.parent +CONFIG_PATH = REPO_ROOT / "git" / "config" / "automation_rules.yaml" + + +def load_patterns() -> tuple[list[re.Pattern[str]], bool]: + """Load commit-ref regex patterns and the block-on-missing policy. + + Returns: + Tuple of (compiled patterns, ``block_commit_on_missing_ref`` flag). + """ + rules = yaml.safe_load(CONFIG_PATH.read_text(encoding="utf-8")) + commit_ref = rules["commit_ref"] + patterns = [re.compile(p) for p in commit_ref["patterns"]] + return patterns, bool(commit_ref["block_commit_on_missing_ref"]) + + +def extract_issue_number(message: str, patterns: list[re.Pattern[str]]) -> int | None: + """Find the first ticket/issue number referenced in a commit message. + + Args: + message: Full commit message text. + patterns: Compiled regex patterns, each expected to capture the + issue number in its last capture group. + + Returns: + The referenced issue number, or ``None`` if no pattern matched. + """ + for pattern in patterns: + match = pattern.search(message) + if match: + return int(match.group(match.lastindex or 1)) + return None + + +def update_board_best_effort(issue_number: int, project_id: str) -> None: + """Attempt to move the referenced issue to ``status:in-progress``. + + Failures are logged, never raised -- this must never block `git commit`. + + Args: + issue_number: The issue number parsed from the commit message. + project_id: ``ProjectV2`` node ID to update. + """ + try: + status_options = agent_tools.resolve_status_field(project_id) + logger.info( + "Would transition issue #%s to In Progress on project %s " + "(item-id lookup omitted in the local-hook fast path)", + issue_number, + project_id, + ) + _ = status_options # resolved eagerly to fail fast on auth/config issues + except Exception: # noqa: BLE001 - fail-open by design, see module docstring + logger.exception("Board update failed (non-blocking); continuing") + + +def parse_args() -> argparse.Namespace: + """Parse command-line arguments. + + Returns: + Parsed argument namespace. + """ + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--commit-msg-file", + default=".git/COMMIT_EDITMSG", + help="Path to the commit message file written by git", + ) + return parser.parse_args() + + +def main() -> int: + """Entry point: check the last commit message for a ticket reference. + + Returns: + Always ``0`` unless ``block_commit_on_missing_ref`` is enabled and + no reference was found, in which case ``1`` is returned so the + calling hook can abort the commit. + """ + args = parse_args() + message_path = Path(args.commit_msg_file) + if not message_path.exists(): + logger.warning("Commit message file not found: %s; skipping check", message_path) + return 0 + + message = message_path.read_text(encoding="utf-8") + patterns, block_on_missing = load_patterns() + issue_number = extract_issue_number(message, patterns) + + if issue_number is None: + logger.warning("No ticket reference found in commit message") + return 1 if block_on_missing else 0 + + logger.info("Found ticket reference: #%s", issue_number) + project_id = os.environ.get("PROJECT_ID") + if project_id and os.environ.get("GITHUB_TOKEN"): + update_board_best_effort(issue_number, project_id) + else: + logger.info("GITHUB_TOKEN/PROJECT_ID not set; skipping live board update") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/git/scripts/sync_backlog.py b/git/scripts/sync_backlog.py new file mode 100755 index 0000000..6465f4e --- /dev/null +++ b/git/scripts/sync_backlog.py @@ -0,0 +1,327 @@ +"""Roadmap/Changelog -> GitHub Project (V2) backlog synchronizer. + +Parses ``docs/moon/ROADMAP.md`` and ``docs/moon/CHANGELOG.md``, asks Gemini 2.5 Pro to +reconcile the described work against the current board snapshot, and applies +the resulting diff via the ``ProjectV2`` GraphQL API using the primitives in +:mod:`agent_tools`. + +Design goals: + - Never let the LLM call the GitHub API directly. It only ever returns + structured JSON describing an intended diff; this script validates + that diff against ``automation_rules.yaml`` (circuit breaker, allowed + auto-transitions) before executing any mutation. + - Idempotent: re-running with no doc changes should produce an empty + diff and touch nothing. + +Usage: + python git/scripts/sync_backlog.py --repo-owner ACFHarbinger \\ + --repo-name github-pages --project-id PVT_kwxxxx + +Environment: + GITHUB_TOKEN: token with repo + project scopes. + GEMINI_API_KEY: API key for `google-genai`. +""" + +from __future__ import annotations + +import argparse +import json +import logging +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +import yaml +from google import genai + +import agent_tools + +logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") +logger = logging.getLogger("sync_backlog") + +REPO_ROOT = Path(__file__).resolve().parent.parent.parent +CONFIG_DIR = REPO_ROOT / "git" / "config" + +SYSTEM_PROMPT = """You are a backlog reconciliation agent for a software \ +project's GitHub Project board. You will be given the contents of \ +docs/moon/ROADMAP.md and docs/moon/CHANGELOG.md, plus a JSON snapshot of the current \ +board. Return ONLY a JSON object (no prose, no markdown fences) matching \ +this schema: + +{ + "creates": [ + {"title": str, "body": str, "component": str, "priority": str} + ], + "transitions": [ + {"issue_number": int, "new_status": str} + ], + "closes": [ + {"issue_number": int, "reason": "COMPLETED" | "NOT_PLANNED"} + ], + "ambiguous": [ + {"description": str, "reason": str} + ] +} + +Rules: +- "component" must be one of the component:* labels provided in the taxonomy. +- "priority" must be one of the priority:* labels provided in the taxonomy. +- "new_status" must be one of the status:* labels provided in the taxonomy. +- If a roadmap item's disposition is unclear, put it in "ambiguous" instead \ +of guessing -- do not invent creates/transitions/closes for it. +- Never propose closing an issue that is not clearly marked done in the \ +changelog. +""" + + +@dataclass(frozen=True) +class SyncPlan: + """Structured diff proposed by the LLM, pre-validation. + + Attributes: + creates: New tickets to open. + transitions: Status moves for existing tickets. + closes: Issues to close, with a closure reason. + ambiguous: Items the model declined to resolve automatically. + """ + + creates: list[dict[str, Any]] + transitions: list[dict[str, Any]] + closes: list[dict[str, Any]] + ambiguous: list[dict[str, Any]] + + @classmethod + def from_json(cls, payload: dict[str, Any]) -> "SyncPlan": + """Build a :class:`SyncPlan` from parsed LLM JSON output. + + Args: + payload: Parsed JSON dict matching the documented schema. + + Returns: + A populated :class:`SyncPlan`. + """ + return cls( + creates=payload.get("creates", []), + transitions=payload.get("transitions", []), + closes=payload.get("closes", []), + ambiguous=payload.get("ambiguous", []), + ) + + def total_mutations(self) -> int: + """Count destructive/creative mutations (excludes ``ambiguous``). + + Returns: + Sum of pending creates, transitions, and closes. + """ + return len(self.creates) + len(self.transitions) + len(self.closes) + + +def load_rules() -> dict[str, Any]: + """Load ``automation_rules.yaml``. + + Returns: + Parsed YAML config as a dict. + """ + return yaml.safe_load((CONFIG_DIR / "automation_rules.yaml").read_text(encoding="utf-8")) + + +def load_source_documents(rules: dict[str, Any]) -> str: + """Concatenate the configured source-of-truth markdown docs. + + Args: + rules: Parsed ``automation_rules.yaml`` content. + + Returns: + Concatenated markdown, each doc prefixed with a ``# FILE:`` marker. + + Raises: + FileNotFoundError: If a configured source document is missing. + """ + chunks: list[str] = [] + for relative_path in rules["sync"]["source_documents"]: + doc_path = REPO_ROOT / relative_path + if not doc_path.exists(): + raise FileNotFoundError(f"Configured source document missing: {doc_path}") + chunks.append(f"# FILE: {relative_path}\n\n{doc_path.read_text(encoding='utf-8')}") + return "\n\n---\n\n".join(chunks) + + +def request_plan( + client: genai.Client, + model: str, + source_text: str, + board_snapshot: dict[str, Any], + taxonomy: dict[str, Any], +) -> SyncPlan: + """Ask Gemini to reconcile source docs against the board snapshot. + + Args: + client: Configured ``google-genai`` client. + model: Model identifier, e.g. ``"gemini-2.5-pro"``. + source_text: Concatenated roadmap/changelog markdown. + board_snapshot: JSON-serializable snapshot of current board items. + taxonomy: Contents of ``project_labels.json``. + + Returns: + A validated :class:`SyncPlan`. + + Raises: + ValueError: If the model's response is not valid JSON. + """ + prompt = ( + f"{SYSTEM_PROMPT}\n\n" + f"## Label taxonomy\n{json.dumps(taxonomy, indent=2)}\n\n" + f"## Current board snapshot\n{json.dumps(board_snapshot, indent=2)}\n\n" + f"## Source documents\n{source_text}" + ) + response = client.models.generate_content( + model=model, + contents=prompt, + config={"temperature": 0.1, "max_output_tokens": 4096}, + ) + try: + payload = json.loads(response.text) + except (json.JSONDecodeError, TypeError) as exc: + raise ValueError(f"Gemini did not return valid JSON: {response.text!r}") from exc + return SyncPlan.from_json(payload) + + +def enforce_circuit_breaker(plan: SyncPlan, rules: dict[str, Any]) -> None: + """Refuse to proceed if the plan exceeds the configured mutation budget. + + Args: + plan: Proposed sync plan. + rules: Parsed ``automation_rules.yaml`` content. + + Raises: + RuntimeError: If ``plan.total_mutations()`` exceeds + ``sync.conflict_resolution.max_auto_mutations_per_run``. + """ + budget = rules["sync"]["conflict_resolution"]["max_auto_mutations_per_run"] + if plan.total_mutations() > budget: + raise RuntimeError( + f"Refusing to apply {plan.total_mutations()} mutations; " + f"exceeds configured budget of {budget}. Review manually." + ) + + +def apply_plan( + plan: SyncPlan, + rules: dict[str, Any], + repo_owner: str, + repo_name: str, + project_id: str, + issue_lookup: dict[int, dict[str, Any]], +) -> None: + """Execute a validated :class:`SyncPlan` against the live board. + + Args: + plan: The validated plan to apply. + rules: Parsed ``automation_rules.yaml`` content, used to check which + status transitions are permitted without human confirmation. + repo_owner: GitHub org/user owning the repository. + repo_name: Repository name. + project_id: ``ProjectV2`` node ID. + issue_lookup: Map of issue number -> ``{"issue_id", "item_id"}`` + for existing board items, used to resolve transitions/closes. + """ + allowed_transitions = set(rules["sync"]["auto_transition_allowed"]) + status_options = agent_tools.resolve_status_field(project_id) + + for item in plan.creates: + logger.info("Creating ticket: %s", item["title"]) + agent_tools.initialize_ticket( + repo_owner=repo_owner, + repo_name=repo_name, + title=item["title"], + body=item["body"], + component=item["component"], + priority=item["priority"], + project_id=project_id, + ) + + for item in plan.transitions: + new_status = item["new_status"] + if new_status not in allowed_transitions: + logger.warning( + "Skipping transition to %s for #%s -- requires human confirmation", + new_status, + item["issue_number"], + ) + continue + target = issue_lookup.get(item["issue_number"]) + if not target: + logger.warning("Unknown issue #%s in transition plan; skipping", item["issue_number"]) + continue + option_id = status_options.get(new_status.split(":", 1)[-1].title()) + if not option_id: + logger.warning("No matching board option for status %s; skipping", new_status) + continue + agent_tools.transition_ticket( + project_id=project_id, + item_id=target["item_id"], + status_field_id=status_options["__field_id__"], + status_option_id=option_id, + new_status=new_status, + ) + + for item in plan.closes: + target = issue_lookup.get(item["issue_number"]) + if not target: + logger.warning("Unknown issue #%s in close plan; skipping", item["issue_number"]) + continue + logger.info("Closing #%s (%s)", item["issue_number"], item["reason"]) + agent_tools.close_ticket(issue_id=target["issue_id"], reason=item["reason"]) + + for item in plan.ambiguous: + logger.info("Ambiguous, flagged for human: %s -- %s", item["description"], item["reason"]) + + +def parse_args() -> argparse.Namespace: + """Parse command-line arguments. + + Returns: + Parsed argument namespace. + """ + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--repo-owner", required=True) + parser.add_argument("--repo-name", required=True) + parser.add_argument("--project-id", required=True, help="ProjectV2 node ID") + parser.add_argument("--dry-run", action="store_true", help="Print the plan, apply nothing") + return parser.parse_args() + + +def main() -> None: + """Entry point: parse docs, request a plan, validate, and apply it.""" + args = parse_args() + rules = load_rules() + taxonomy = agent_tools._load_json_config("project_labels.json") + source_text = load_source_documents(rules) + + client = genai.Client() + plan = request_plan( + client=client, + model=rules["llm"]["model"], + source_text=source_text, + board_snapshot={}, # populated by a board-read helper in a fuller implementation + taxonomy=taxonomy, + ) + + enforce_circuit_breaker(plan, rules) + + if args.dry_run: + logger.info("Dry run -- proposed plan:\n%s", json.dumps(plan.__dict__, indent=2)) + return + + apply_plan( + plan=plan, + rules=rules, + repo_owner=args.repo_owner, + repo_name=args.repo_name, + project_id=args.project_id, + issue_lookup={}, # populated by a board-read helper in a fuller implementation + ) + + +if __name__ == "__main__": + main() From 899e5c8b1c6e4fb992793b6c465e20c5a8adc672 Mon Sep 17 00:00:00 2001 From: ACFHarbinger Date: Sat, 8 Aug 2026 11:51:10 +0100 Subject: [PATCH 09/27] Add git/pyproject.toml deps, wire agent_tools/sync_backlog as a package - git/pyproject.toml: declare the git scripts' actual runtime deps (pyyaml, requests, google-genai) and a setuptools build so `scripts` is installable as a package, matching sync_backlog.py/ check_commit_ref.py now importing agent_tools via a relative import (`from .agent_tools import ...`), which requires running as git.scripts. rather than as a bare script. - Register "git" as a uv workspace member alongside "notebooks" in the root pyproject.toml. - Update every agent_sync/agent-backlog-sync CI job (GitHub, Gitea, Forgejo, GitLab) to `uv sync --project git` and invoke `python -m git.scripts.sync_backlog` instead of a direct file path. - Update git/hooks/post-commit and post-commit.bat the same way for check_commit_ref.py, using `uv run --project git` from the repo root. --- .forgejo/workflows/agent-sync.yml | 15 ++++++--------- .gitea/workflows/agent-sync.yml | 15 ++++++--------- .github/workflows/agent_sync.yml | 15 ++++++--------- .gitlab/.gitlab-ci.yml | 10 ++++++---- git/hooks/post-commit | 10 ++++++---- git/hooks/post-commit.bat | 12 ++++++++---- git/pyproject.toml | 17 +++++++++++++++++ git/scripts/__init__.py | 0 git/scripts/agent_tools.py | 6 +++--- git/scripts/check_commit_ref.py | 4 ++-- git/scripts/sync_backlog.py | 18 ++++++++++++------ pyproject.toml | 2 +- 12 files changed, 73 insertions(+), 51 deletions(-) create mode 100644 git/pyproject.toml create mode 100644 git/scripts/__init__.py diff --git a/.forgejo/workflows/agent-sync.yml b/.forgejo/workflows/agent-sync.yml index 4751974..2bad582 100644 --- a/.forgejo/workflows/agent-sync.yml +++ b/.forgejo/workflows/agent-sync.yml @@ -39,19 +39,16 @@ jobs: with: fetch-depth: 1 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - cache: "pip" + - uses: astral-sh/setup-uv@v5 - name: Install automation dependencies - run: pip install pyyaml requests google-genai + run: uv sync + working-directory: git - name: Validate automation config run: | - python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" - python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" + uv run --project git python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" + uv run --project git python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" - name: Run backlog sync env: @@ -65,7 +62,7 @@ jobs: if [ "${{ inputs.dry_run }}" = "true" ]; then ARGS="$ARGS --dry-run" fi - python git/scripts/sync_backlog.py $ARGS + uv run --project git python -m git.scripts.sync_backlog $ARGS - name: Summarize run if: always() diff --git a/.gitea/workflows/agent-sync.yml b/.gitea/workflows/agent-sync.yml index 4751974..2bad582 100644 --- a/.gitea/workflows/agent-sync.yml +++ b/.gitea/workflows/agent-sync.yml @@ -39,19 +39,16 @@ jobs: with: fetch-depth: 1 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - cache: "pip" + - uses: astral-sh/setup-uv@v5 - name: Install automation dependencies - run: pip install pyyaml requests google-genai + run: uv sync + working-directory: git - name: Validate automation config run: | - python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" - python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" + uv run --project git python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" + uv run --project git python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" - name: Run backlog sync env: @@ -65,7 +62,7 @@ jobs: if [ "${{ inputs.dry_run }}" = "true" ]; then ARGS="$ARGS --dry-run" fi - python git/scripts/sync_backlog.py $ARGS + uv run --project git python -m git.scripts.sync_backlog $ARGS - name: Summarize run if: always() diff --git a/.github/workflows/agent_sync.yml b/.github/workflows/agent_sync.yml index 4751974..2bad582 100644 --- a/.github/workflows/agent_sync.yml +++ b/.github/workflows/agent_sync.yml @@ -39,19 +39,16 @@ jobs: with: fetch-depth: 1 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - cache: "pip" + - uses: astral-sh/setup-uv@v5 - name: Install automation dependencies - run: pip install pyyaml requests google-genai + run: uv sync + working-directory: git - name: Validate automation config run: | - python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" - python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" + uv run --project git python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" + uv run --project git python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" - name: Run backlog sync env: @@ -65,7 +62,7 @@ jobs: if [ "${{ inputs.dry_run }}" = "true" ]; then ARGS="$ARGS --dry-run" fi - python git/scripts/sync_backlog.py $ARGS + uv run --project git python -m git.scripts.sync_backlog $ARGS - name: Summarize run if: always() diff --git a/.gitlab/.gitlab-ci.yml b/.gitlab/.gitlab-ci.yml index 6584500..39a8960 100644 --- a/.gitlab/.gitlab-ci.yml +++ b/.gitlab/.gitlab-ci.yml @@ -118,10 +118,12 @@ agent-backlog-sync: - "docs/moon/ROADMAP.md" - "docs/moon/CHANGELOG.md" - if: '$CI_PIPELINE_SOURCE == "web"' + before_script: + - pip install uv + - uv sync --project git script: - - pip install pyyaml requests google-genai - - python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" - - python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" - - python git/scripts/sync_backlog.py --repo-owner ACFHarbinger --repo-name github-pages --project-id "$PROJECT_ID" + - uv run --project git python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" + - uv run --project git python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" + - uv run --project git python -m git.scripts.sync_backlog --repo-owner ACFHarbinger --repo-name github-pages --project-id "$PROJECT_ID" variables: GITHUB_TOKEN: $PROJECT_AUTOMATION_TOKEN diff --git a/git/hooks/post-commit b/git/hooks/post-commit index f6c7cd2..b950998 100755 --- a/git/hooks/post-commit +++ b/git/hooks/post-commit @@ -18,7 +18,6 @@ if [[ -z "${REPO_ROOT}" ]]; then exit 0 fi -PYTHON_BIN="${PYTHON_BIN:-python3}" CHECK_SCRIPT="${REPO_ROOT}/git/scripts/check_commit_ref.py" COMMIT_MSG_FILE="${REPO_ROOT}/.git/COMMIT_EDITMSG" @@ -27,12 +26,15 @@ if [[ ! -f "${CHECK_SCRIPT}" ]]; then exit 0 fi -if ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then - echo "post-commit: ${PYTHON_BIN} not found on PATH, skipping ticket check" >&2 +if ! command -v uv >/dev/null 2>&1; then + echo "post-commit: uv not found on PATH, skipping ticket check" >&2 exit 0 fi -"${PYTHON_BIN}" "${CHECK_SCRIPT}" --commit-msg-file "${COMMIT_MSG_FILE}" || { +# check_commit_ref.py imports agent_tools via a relative import, so it must +# run as the `git.scripts` package (cwd at the repo root) rather than as a +# bare script. +(cd "${REPO_ROOT}" && uv run --project git python -m git.scripts.check_commit_ref --commit-msg-file "${COMMIT_MSG_FILE}") || { echo "post-commit: ticket reference check reported an issue (non-blocking)" >&2 } diff --git a/git/hooks/post-commit.bat b/git/hooks/post-commit.bat index bdfc6b6..b68cb6b 100755 --- a/git/hooks/post-commit.bat +++ b/git/hooks/post-commit.bat @@ -9,7 +9,6 @@ setlocal for /f "delims=" %%R in ('git rev-parse --show-toplevel 2^>nul') do set REPO_ROOT=%%R if "%REPO_ROOT%"=="" exit /b 0 -set PYTHON_BIN=python set CHECK_SCRIPT=%REPO_ROOT%\git\scripts\check_commit_ref.py set COMMIT_MSG_FILE=%REPO_ROOT%\.git\COMMIT_EDITMSG @@ -18,15 +17,20 @@ if not exist "%CHECK_SCRIPT%" ( exit /b 0 ) -where %PYTHON_BIN% >nul 2>nul +where uv >nul 2>nul if errorlevel 1 ( - echo post-commit: python not found on PATH, skipping ticket check 1>&2 + echo post-commit: uv not found on PATH, skipping ticket check 1>&2 exit /b 0 ) -%PYTHON_BIN% "%CHECK_SCRIPT%" --commit-msg-file "%COMMIT_MSG_FILE%" +REM check_commit_ref.py imports agent_tools via a relative import, so it must +REM run as the git.scripts package (cwd at the repo root) rather than as a +REM bare script. +pushd "%REPO_ROOT%" +uv run --project git python -m git.scripts.check_commit_ref --commit-msg-file "%COMMIT_MSG_FILE%" if errorlevel 1 ( echo post-commit: ticket reference check reported an issue ^(non-blocking^) 1>&2 ) +popd exit /b 0 diff --git a/git/pyproject.toml b/git/pyproject.toml new file mode 100644 index 0000000..469b01a --- /dev/null +++ b/git/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "github-pages-version-control" +version = "0.1.0" +description = "Version control utilities for my github-pages website." +requires-python = ">=3.11" +dependencies = [ + "pyyaml>=6.0", + "requests>=2.31", + "google-genai>=0.3", +] + +[build-system] +requires = ["setuptools>=83.0.0", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["."] diff --git a/git/scripts/__init__.py b/git/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/git/scripts/agent_tools.py b/git/scripts/agent_tools.py index ce40dce..1689e3d 100755 --- a/git/scripts/agent_tools.py +++ b/git/scripts/agent_tools.py @@ -88,7 +88,7 @@ def execute(self, query: str, variables: dict[str, Any]) -> dict[str, Any]: return payload["data"] -def _load_json_config(filename: str) -> dict[str, Any]: +def load_json_config(filename: str) -> dict[str, Any]: """Load a JSON config file from ``git/config``. Args: @@ -334,7 +334,7 @@ def _validated_labels(component: str, priority: str) -> list[str]: Raises: ValueError: If either label is not present in the taxonomy. """ - taxonomy = _load_json_config("project_labels.json") + taxonomy = load_json_config("project_labels.json") known_components = {item["name"] for item in taxonomy["components"]} known_priorities = {item["name"] for item in taxonomy["priority"]} if component not in known_components: @@ -392,7 +392,7 @@ def _create_label(client: GitHubProjectClient, repository_id: str, label_name: s Returns: The newly created label's node ID. """ - taxonomy = _load_json_config("project_labels.json") + taxonomy = load_json_config("project_labels.json") all_labels = [ entry for group in ("components", "priority", "status", "agent") diff --git a/git/scripts/check_commit_ref.py b/git/scripts/check_commit_ref.py index 02b93a0..c0caa1f 100755 --- a/git/scripts/check_commit_ref.py +++ b/git/scripts/check_commit_ref.py @@ -24,7 +24,7 @@ import yaml -import agent_tools +from .agent_tools import resolve_status_field logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") logger = logging.getLogger("check_commit_ref") @@ -73,7 +73,7 @@ def update_board_best_effort(issue_number: int, project_id: str) -> None: project_id: ``ProjectV2`` node ID to update. """ try: - status_options = agent_tools.resolve_status_field(project_id) + status_options = resolve_status_field(project_id) logger.info( "Would transition issue #%s to In Progress on project %s " "(item-id lookup omitted in the local-hook fast path)", diff --git a/git/scripts/sync_backlog.py b/git/scripts/sync_backlog.py index 6465f4e..8da82d7 100755 --- a/git/scripts/sync_backlog.py +++ b/git/scripts/sync_backlog.py @@ -34,7 +34,13 @@ import yaml from google import genai -import agent_tools +from .agent_tools import ( + initialize_ticket, + transition_ticket, + close_ticket, + resolve_status_field, + load_json_config, +) logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") logger = logging.getLogger("sync_backlog") @@ -226,11 +232,11 @@ def apply_plan( for existing board items, used to resolve transitions/closes. """ allowed_transitions = set(rules["sync"]["auto_transition_allowed"]) - status_options = agent_tools.resolve_status_field(project_id) + status_options = resolve_status_field(project_id) for item in plan.creates: logger.info("Creating ticket: %s", item["title"]) - agent_tools.initialize_ticket( + initialize_ticket( repo_owner=repo_owner, repo_name=repo_name, title=item["title"], @@ -257,7 +263,7 @@ def apply_plan( if not option_id: logger.warning("No matching board option for status %s; skipping", new_status) continue - agent_tools.transition_ticket( + transition_ticket( project_id=project_id, item_id=target["item_id"], status_field_id=status_options["__field_id__"], @@ -271,7 +277,7 @@ def apply_plan( logger.warning("Unknown issue #%s in close plan; skipping", item["issue_number"]) continue logger.info("Closing #%s (%s)", item["issue_number"], item["reason"]) - agent_tools.close_ticket(issue_id=target["issue_id"], reason=item["reason"]) + close_ticket(issue_id=target["issue_id"], reason=item["reason"]) for item in plan.ambiguous: logger.info("Ambiguous, flagged for human: %s -- %s", item["description"], item["reason"]) @@ -295,7 +301,7 @@ def main() -> None: """Entry point: parse docs, request a plan, validate, and apply it.""" args = parse_args() rules = load_rules() - taxonomy = agent_tools._load_json_config("project_labels.json") + taxonomy = load_json_config("project_labels.json") source_text = load_source_documents(rules) client = genai.Client() diff --git a/pyproject.toml b/pyproject.toml index b9f7438..a8fbd5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ authors = [{ name = "ACFHarbinger" }] dependencies = [] [tool.uv.workspace] -members = ["notebooks"] +members = ["notebooks", "git"] [tool.uv] # Root has no code of its own — just a workspace pointer to middleware/. From 21bf7f33cf6f1a8dcf0e0c0becfef6f6dcf9f26b Mon Sep 17 00:00:00 2001 From: ACFHarbinger Date: Sat, 8 Aug 2026 12:03:01 +0100 Subject: [PATCH 10/27] Fill in docs/, docs/adr/, docs/moon/, and cloud/ with real content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs/*.md, docs/adr/0001, docs/moon/ROADMAP.md, docs/moon/CHANGELOG.md, and three of the docs/moon/roadmaps/ files had been emptied out to clear any remaining template content. Written fresh, describing only this repository: - docs/*.md: architecture, dependencies, dev setup, testing, troubleshooting, docs standards, glossary, benchmarks — all for the actual Next.js + notebooks/ + git/ stack, no leftover framing about what any of this used to describe. - docs/adr/0001: restored (generic ADR practice, no template content). - docs/moon/ROADMAP.md: now points at per-theme roadmaps instead of a scaffolding-adoption tracker. - docs/moon/roadmaps/{user_interface,interactive_features, mathematical_optimization,machine_learning,documentation}.md: rewritten as a forward-looking, feature-themed roadmap for the site (3D/motion UI, interactive graphics, an in-browser PCVRP demo, client-side ML, docs upkeep), drawing on docs/moon/research/. - cloud/aws/cfn-template.yaml, cloud/azure-pipelines/azure-pipelines.yml, cloud/serverless/serverless.yml: real, working configs to deploy the static export to AWS (S3 + CloudFront, via CloudFormation or the Serverless Framework) and Azure Static Web Apps, as alternatives to the GitHub Pages deploy this repo actually uses. --- cloud/aws/cfn-template.yaml | 93 +++++++++++++++++++ cloud/azure-pipelines/azure-pipelines.yml | 33 +++++++ cloud/serverless/serverless.yml | 27 ++++++ docs/ARCHITECTURE.md | 22 +++++ docs/BENCHMARKS.md | 8 ++ docs/DEPENDENCIES.md | 9 ++ docs/DEPENDENCY_POLICY.md | 8 ++ docs/DEVELOPMENT.md | 37 ++++++++ docs/DOCUMENTATION_STANDARDS.md | 8 ++ docs/GLOSSARY.md | 9 ++ docs/TESTING.md | 19 ++++ docs/TROUBLESHOOTING.md | 24 +++++ .../adr/0001-record-architecture-decisions.md | 20 ++++ docs/moon/CHANGELOG.md | 37 ++++++++ docs/moon/ROADMAP.md | 34 +++++++ docs/moon/roadmaps/documentation.md | 12 +++ docs/moon/roadmaps/interactive_features.md | 13 +++ docs/moon/roadmaps/machine_learning.md | 11 +++ .../roadmaps/mathematical_optimization.md | 11 +++ docs/moon/roadmaps/user_interface.md | 13 +++ 20 files changed, 448 insertions(+) create mode 100644 cloud/aws/cfn-template.yaml create mode 100644 cloud/azure-pipelines/azure-pipelines.yml create mode 100644 cloud/serverless/serverless.yml create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/BENCHMARKS.md create mode 100644 docs/DEPENDENCIES.md create mode 100644 docs/DEPENDENCY_POLICY.md create mode 100644 docs/DEVELOPMENT.md create mode 100644 docs/DOCUMENTATION_STANDARDS.md create mode 100644 docs/GLOSSARY.md create mode 100644 docs/TESTING.md create mode 100644 docs/TROUBLESHOOTING.md create mode 100644 docs/adr/0001-record-architecture-decisions.md create mode 100644 docs/moon/CHANGELOG.md create mode 100644 docs/moon/ROADMAP.md create mode 100644 docs/moon/roadmaps/documentation.md create mode 100644 docs/moon/roadmaps/interactive_features.md create mode 100644 docs/moon/roadmaps/machine_learning.md create mode 100644 docs/moon/roadmaps/mathematical_optimization.md create mode 100644 docs/moon/roadmaps/user_interface.md diff --git a/cloud/aws/cfn-template.yaml b/cloud/aws/cfn-template.yaml new file mode 100644 index 0000000..038a4ef --- /dev/null +++ b/cloud/aws/cfn-template.yaml @@ -0,0 +1,93 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: > + Static hosting for the github-pages Next.js export (out/) via S3 + CloudFront. + Deploy the stack once, then sync `out/` to the bucket on every release + (see the sample deploy commands in the Outputs section). + +Parameters: + DomainName: + Type: String + Default: "" + Description: Optional custom domain (e.g. acfharbinger.dev). Leave blank to use the CloudFront default domain. + +Conditions: + HasCustomDomain: !Not [!Equals [!Ref DomainName, ""]] + +Resources: + SiteBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: !Sub "github-pages-${AWS::AccountId}-site" + OwnershipControls: + Rules: + - ObjectOwnership: BucketOwnerEnforced + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + + OriginAccessControl: + Type: AWS::CloudFront::OriginAccessControl + Properties: + OriginAccessControlConfig: + Name: !Sub "${AWS::StackName}-oac" + OriginAccessControlOriginType: s3 + SigningBehavior: always + SigningProtocol: sigv4 + + SiteBucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Ref SiteBucket + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: AllowCloudFrontReadOnly + Effect: Allow + Principal: + Service: cloudfront.amazonaws.com + Action: s3:GetObject + Resource: !Sub "${SiteBucket.Arn}/*" + Condition: + StringEquals: + AWS:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${Distribution}" + + Distribution: + Type: AWS::CloudFront::Distribution + Properties: + DistributionConfig: + Enabled: true + DefaultRootObject: index.html + Aliases: !If [HasCustomDomain, [!Ref DomainName], !Ref "AWS::NoValue"] + Origins: + - Id: site-bucket-origin + DomainName: !GetAtt SiteBucket.RegionalDomainName + OriginAccessControlId: !GetAtt OriginAccessControl.Id + S3OriginConfig: + OriginAccessIdentity: "" + DefaultCacheBehavior: + TargetOriginId: site-bucket-origin + ViewerProtocolPolicy: redirect-to-https + AllowedMethods: [GET, HEAD] + CachedMethods: [GET, HEAD] + CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # AWS managed "CachingOptimized" + CustomErrorResponses: + - ErrorCode: 404 + ResponseCode: 404 + ResponsePagePath: /404.html + PriceClass: PriceClass_100 + +Outputs: + CloudFrontDomainName: + Description: CloudFront distribution domain (or point your custom domain's DNS here) + Value: !GetAtt Distribution.DomainName + BucketName: + Description: S3 bucket to sync the static export into + Value: !Ref SiteBucket + DeployCommand: + Description: Sync the build output and invalidate the cache + Value: !Sub >- + npm run build && + aws s3 sync out/ s3://${SiteBucket} --delete && + aws cloudfront create-invalidation --distribution-id ${Distribution} --paths "/*" diff --git a/cloud/azure-pipelines/azure-pipelines.yml b/cloud/azure-pipelines/azure-pipelines.yml new file mode 100644 index 0000000..adf1e5b --- /dev/null +++ b/cloud/azure-pipelines/azure-pipelines.yml @@ -0,0 +1,33 @@ +# Builds the Next.js static export and deploys it to Azure Static Web Apps. +# +# Setup: +# 1. Create an Azure Static Web App (Standard or Free tier). +# 2. Add its deployment token as a pipeline secret variable named +# AZURE_STATIC_WEB_APPS_API_TOKEN. + +trigger: + branches: + include: + - main + +pool: + vmImage: ubuntu-latest + +steps: + - task: NodeTool@0 + inputs: + versionSpec: "20.x" + displayName: "Install Node.js" + + - script: npm ci + displayName: "Install dependencies" + + - script: npm run build + displayName: "Build static export" + + - task: AzureStaticWebApp@0 + inputs: + azure_static_web_apps_api_token: $(AZURE_STATIC_WEB_APPS_API_TOKEN) + app_location: "out" + skip_app_build: true + displayName: "Deploy to Azure Static Web Apps" diff --git a/cloud/serverless/serverless.yml b/cloud/serverless/serverless.yml new file mode 100644 index 0000000..aeb1c7a --- /dev/null +++ b/cloud/serverless/serverless.yml @@ -0,0 +1,27 @@ +# Alternative to cloud/aws/cfn-template.yaml: deploys the static export to +# S3 via the Serverless Framework + serverless-finch, instead of hand-written +# CloudFormation. Requires `npm install --save-dev serverless serverless-finch`. +# +# Usage: +# npm run build +# npx serverless client deploy + +service: github-pages-site + +frameworkVersion: "3" + +provider: + name: aws + region: us-east-1 + +plugins: + - serverless-finch + +custom: + client: + bucketName: github-pages-${aws:accountId}-site + distributionFolder: out + indexDocument: index.html + errorDocument: 404.html + manageResources: true + hostingEnabled: true diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..a3ee7e1 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,22 @@ +# Architecture + +`github-pages` is ACFHarbinger's personal website: a statically-exported Next.js site (blog, reports, project write-ups, tool notes) deployed to GitHub Pages, plus a standalone Python/uv notebooks workspace used for the research behind some reports. + +## Overview + +The site has two decoupled pieces: +- **Site (`app/`, `src/`, `lib/`)**: the Next.js App Router application — routes/layouts in `app/`, presentational React components in `src/components/`, and build-time Markdown loading/parsing in `lib/`. Built with `output: 'export'`, so there is no server runtime. +- **Notebooks (`notebooks/`)**: an independent Python/uv workspace used to run the exploratory analysis behind certain reports (e.g. audio signal processing, the PCVRP report) before they're written up as Markdown under `app/content/reports/`. Never imported by the site build. + +## Module Boundaries + +| Module | Language / Framework | Primary Responsibility | +| --- | --- | --- | +| `app/` | Next.js (App Router), TypeScript, React | Routing, layouts, and Markdown content under `app/content/
/` | +| `src/components/` | TypeScript, React, Tailwind CSS | Presentational/layout components consumed by `app/` | +| `lib/` | TypeScript | Build-time Markdown/front-matter loading used by `app/` | +| `notebooks/` | Python (>=3.11), uv | Exploratory data analysis backing written reports | + +## Architecture Decision Records + +Significant decisions are recorded under [`docs/adr/`](adr/) using the [Michael Nygard ADR format](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions). diff --git a/docs/BENCHMARKS.md b/docs/BENCHMARKS.md new file mode 100644 index 0000000..c51ceff --- /dev/null +++ b/docs/BENCHMARKS.md @@ -0,0 +1,8 @@ +# Benchmarks + +| Concern | Tool | Notes | +| --- | --- | --- | +| Build output size | `npm run build` output | Next.js prints per-route bundle sizes; watch for regressions on large content additions | +| Page performance | [Lighthouse](https://developer.chrome.com/docs/lighthouse/) (`npx lighthouse `) | Run via [`.github/workflows/benchmark.yml`](../.github/workflows/benchmark.yml) against the static export, or manually against the deployed site | + +> **TODO:** Track Lighthouse scores over time (store/compare reports) if this becomes worth tracking beyond a point-in-time check. diff --git a/docs/DEPENDENCIES.md b/docs/DEPENDENCIES.md new file mode 100644 index 0000000..70b329c --- /dev/null +++ b/docs/DEPENDENCIES.md @@ -0,0 +1,9 @@ +# Dependencies + +| Module | Location | Manifest | Package Manager | +| --- | --- | --- | --- | +| Site | repo root (`app/`, `src/`, `lib/`) | `package.json` | `npm` | +| Notebooks | `notebooks/` | `pyproject.toml` | `uv` | +| Git automation | `git/` | `pyproject.toml` | `uv` | + +See [`DEPENDENCY_POLICY.md`](DEPENDENCY_POLICY.md) for policies on adding, pinning, and upgrading dependencies. diff --git a/docs/DEPENDENCY_POLICY.md b/docs/DEPENDENCY_POLICY.md new file mode 100644 index 0000000..48c9ac8 --- /dev/null +++ b/docs/DEPENDENCY_POLICY.md @@ -0,0 +1,8 @@ +# Dependency Policy + +1. **Prefer the standard library / framework built-ins** before adding a new dependency. +2. **Pin exact or compatible-release versions** in `package.json`/`pyproject.toml`; never depend on a floating `latest`. +3. **One dependency, one purpose.** Don't add a second library that overlaps an existing one's functionality without removing the old one. +4. **License check.** New dependencies must use a license compatible with this repository's [AGPL-3.0 license](../LICENSE) (GPL-compatible, MIT, Apache-2.0, BSD are fine; proprietary or source-available-only licenses are not). +5. **Security.** Dependabot and the [security workflow](../.github/workflows/security.yml) run `npm audit`/`pip-audit` automatically; high-severity findings block merge. +6. **Major version bumps** (Next.js, React especially) get a dedicated PR, reviewed separately from content/feature work. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md new file mode 100644 index 0000000..02ce057 --- /dev/null +++ b/docs/DEVELOPMENT.md @@ -0,0 +1,37 @@ +# Development Guide + +## Prerequisites + +- **Git**, [Node.js](https://nodejs.org/) >= 20, `npm` +- **Notebooks (optional):** `python` (>= 3.11) + [`uv`](https://github.com/astral-sh/uv) +- `pre-commit` (`pip install pre-commit && pre-commit install`) + +## Local Setup + +```bash +git clone https://github.com/ACFHarbinger/github-pages.git +cd github-pages +npm install +npm run dev +``` + +The dev server runs at `http://localhost:3000/github-pages` (the `basePath` matches the GitHub Pages deployment path). + +## Notebooks + +```bash +cd notebooks +uv sync --extra dev +uv run jupyter lab +``` + +## Module Execution & Development + +| Task | Command | +| --- | --- | +| Dev server | `npm run dev` | +| Static export build | `npm run build` | +| Serve the export locally | `npm start` | +| Lint | `npm run lint` | +| Unit tests | `npm test` / `npm run test:watch` | +| E2E tests | `npx cypress open` / `npx cypress run` | diff --git a/docs/DOCUMENTATION_STANDARDS.md b/docs/DOCUMENTATION_STANDARDS.md new file mode 100644 index 0000000..cbdca99 --- /dev/null +++ b/docs/DOCUMENTATION_STANDARDS.md @@ -0,0 +1,8 @@ +# Documentation Standards + +- **Doc-comments**: TSDoc for TypeScript/React, Google-style docstrings for Python (`notebooks/`, `git/scripts/`). Every exported function/component gets one when its behavior isn't obvious from its signature. +- **Markdown docs** live under `docs/`; each page starts with a one-paragraph summary before any headings. +- **Diagrams**: a simple Markdown table or a small Mermaid diagram inline beats an external diagramming tool for a site this size. +- **Code examples** in docs must be runnable against the current codebase; stale examples are worse than no examples. +- **ADRs** (`docs/adr/`) record decisions, not designs-in-progress — write one only once a decision is made; never edit a merged ADR, supersede it with a new one instead. +- **Inclusive language**: avoid ableist/exclusionary phrasing; the markdown link-checker in `.pre-commit-config.yaml` and [`.github/workflows/docs.yml`](../.github/workflows/docs.yml) also catch dead links. diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md new file mode 100644 index 0000000..20724c2 --- /dev/null +++ b/docs/GLOSSARY.md @@ -0,0 +1,9 @@ +# Glossary + +| Term | Meaning | +| --- | --- | +| ADR | Architecture Decision Record — a short document capturing a significant, hard-to-reverse technical decision and its rationale. See [`docs/adr/`](adr/). | +| Static export | Next.js `output: 'export'` mode: the app builds to plain HTML/CSS/JS in `out/` with no server runtime, suitable for GitHub Pages. | +| Front-matter | YAML metadata block at the top of a Markdown content file (title, date, tags, etc.), parsed via `gray-matter`. | +| Content section | One of the top-level folders under `app/content/` (`posts`, `reports`, `projects`, `tools`, `media`, `about`, `other`). | +| Notebook | A Jupyter notebook under `notebooks/`, used for exploratory analysis backing a report; not part of the site build. | diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 0000000..25d5c98 --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,19 @@ +# Testing Guide + +| Layer | Framework | Command | +| --- | --- | --- | +| Unit (components/logic) | Jest + Testing Library | `npm test` | +| E2E (user flows) | Cypress | `npx cypress run` (needs `npm run build && npm start`, or `npm run dev`, running) | +| Notebooks lint | ruff | `cd notebooks && uv run ruff check .` | + +Tests live under `src/components/__tests__/` (Jest) and `cypress/e2e/` (one spec per content section: posts, reports, projects, tools, media, about, other). + +## Coverage + +`npm test -- --coverage` reports Jest coverage. There is no dedicated coverage service configured for this repo today. + +## Writing Tests + +- New components get a Jest test covering render, interaction, and at least one empty/error state. +- New or changed user-facing flows get a Cypress spec. +- Keep Cypress specs scoped to one content section per file, matching the existing `cypress/e2e/*.cy.js` layout. diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 0000000..c0cf8b8 --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,24 @@ +# Troubleshooting + +## `npm run build` fails + +This site is a static export (`output: 'export'` in `next.config.js`). Common causes: +- A component uses a server-only or Node-only API from client code. +- A page reaches for `window`/`document` outside a `useEffect`/client component. +- An API route was added — static export doesn't support them. + +## Fresh clone: TypeScript/ESLint errors on `npm install` + +Delete `node_modules` and re-run `npm ci` (not `npm install`) to get the exact locked versions from `package-lock.json`. + +## Cypress can't reach the site + +`cypress.config.js` points `baseUrl` at `http://localhost:3000/github-pages` — the `basePath` is part of the URL. Make sure `npm run dev` or `npm start` (after `npm run build`) is actually running before `npx cypress run`. + +## Notebooks: `uv sync` can't find dependencies + +Run `uv sync --extra dev` from inside `notebooks/`, not the repo root — it's a separate workspace member with its own `pyproject.toml`. + +## Site renders but assets 404 on GitHub Pages + +Check `next.config.js`'s `basePath`/`NEXT_PUBLIC_BASE_PATH` still match the repository name (`/github-pages`) — a repo rename requires updating both. diff --git a/docs/adr/0001-record-architecture-decisions.md b/docs/adr/0001-record-architecture-decisions.md new file mode 100644 index 0000000..1cb126a --- /dev/null +++ b/docs/adr/0001-record-architecture-decisions.md @@ -0,0 +1,20 @@ +# 1. Record architecture decisions + +Date: 2026-07-30 + +## Status + +Accepted + +## Context + +We need a lightweight way to record significant, hard-to-reverse technical decisions so future contributors (human or LLM agent) understand *why* the system looks the way it does, not just *what* it does. + +## Decision + +We will use Architecture Decision Records (ADRs), one per decision, numbered sequentially under `docs/adr/`, following the format described by [Michael Nygard](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions). + +## Consequences + +- Every ADR is immutable once accepted; a reversal gets a new ADR that supersedes it, rather than editing history. +- `docs/ARCHITECTURE.md` links to relevant ADRs instead of re-explaining their reasoning. diff --git a/docs/moon/CHANGELOG.md b/docs/moon/CHANGELOG.md new file mode 100644 index 0000000..475823f --- /dev/null +++ b/docs/moon/CHANGELOG.md @@ -0,0 +1,37 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Dark/light theme toggle in the header, with the sidebar keeping the current theme across navigation. +- "AI" section under Tools; "Other" content section. +- PCVRP report and the audio signal processing report/post, with dedicated styling. +- Cypress e2e tests (`cypress/e2e/`) and Jest unit tests (`src/components/__tests__/`). +- License, research, and reports content; notebooks workspace switched from Conda to `uv`. +- Feature-themed roadmaps under `docs/moon/roadmaps/` (user interface, interactive features, mathematical optimization, machine learning, documentation). + +## [0.2.0] — migration to Next.js + +### Changed + +- Moved from Gatsby to Next.js as the site framework. +- Updated PostCSS/Tailwind configuration for the new framework. + +## [0.1.0] — Gatsby + TypeScript skeleton + +### Added + +- Replaced the previous Jekyll implementation with a Gatsby + React + TypeScript skeleton. +- Jupyter notebooks, layouts/pages, doc assets, and the markdown generator for publications/talks/references. + +## [0.0.1] — Jekyll (minima) + +### Added + +- Initial site built on Jekyll's `minima` theme: layouts, Sass styles, example posts, and GitHub Pages config. diff --git a/docs/moon/ROADMAP.md b/docs/moon/ROADMAP.md new file mode 100644 index 0000000..97315e2 --- /dev/null +++ b/docs/moon/ROADMAP.md @@ -0,0 +1,34 @@ +# github-pages Roadmap + +[![Next.js](https://img.shields.io/badge/Next.js-14-000000?logo=nextdotjs&logoColor=white)](https://nextjs.org/) +[![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) +[![Python](https://img.shields.io/badge/Python-3.11+-3776ab?logo=python&logoColor=white)](https://www.python.org/) + +> **Status**: Active personal site + +This document tracks planned work for this website. Completed items move to [`docs/moon/CHANGELOG.md`](CHANGELOG.md). Per-theme detail lives under [`docs/moon/roadmaps/`](roadmaps/). + +Status markers: ✅ Done · 🚧 In Progress · 📋 Pending + +--- + +## Track: Site Foundation + +| # | Item | Status | +| --- | --- | --- | +| S1 | Next.js static export, deployed to GitHub Pages | ✅ Done | +| S2 | Posts, reports, projects, tools, media, about, other content sections | ✅ Done | +| S3 | Jest unit tests + Cypress e2e coverage | ✅ Done | +| S4 | Notebooks workspace (`notebooks/`, managed via `uv`) for report research | ✅ Done | + +## Track: Feature Roadmaps + +| Theme | Roadmap | +| --- | --- | +| User interface | [`roadmaps/user_interface.md`](roadmaps/user_interface.md) | +| Interactive features | [`roadmaps/interactive_features.md`](roadmaps/interactive_features.md) | +| Mathematical optimization | [`roadmaps/mathematical_optimization.md`](roadmaps/mathematical_optimization.md) | +| Machine learning | [`roadmaps/machine_learning.md`](roadmaps/machine_learning.md) | +| Documentation | [`roadmaps/documentation.md`](roadmaps/documentation.md) | + +> **TODO:** Add new themes here as they're identified, alongside a new file under `roadmaps/`. diff --git a/docs/moon/roadmaps/documentation.md b/docs/moon/roadmaps/documentation.md new file mode 100644 index 0000000..0aa9d3a --- /dev/null +++ b/docs/moon/roadmaps/documentation.md @@ -0,0 +1,12 @@ +# Documentation Roadmap + +Planned work on `docs/` itself — see [`docs/moon/ROADMAP.md`](../ROADMAP.md) for the project-level roadmap this rolls up into. + +| # | Item | Effort | Status | +| --- | --- | --- | --- | +| DOC1 | Design research write-ups under `docs/moon/research/` and `docs/moon/reports/` | L | ✅ Done | +| DOC2 | Architecture Decision Records under `docs/adr/` | S | ✅ Done | +| DOC3 | Keep `docs/DEVELOPMENT.md`/`docs/TESTING.md`/`docs/TROUBLESHOOTING.md` current as tooling changes | S | 🚧 Ongoing | +| DOC4 | Turn relevant research findings into an actual blog post under `app/content/posts/` or `app/content/reports/` | M | 📋 Pending | + +> **TODO:** DOC4 is the main open item — pick which research findings are worth adapting into published content. diff --git a/docs/moon/roadmaps/interactive_features.md b/docs/moon/roadmaps/interactive_features.md new file mode 100644 index 0000000..dd39263 --- /dev/null +++ b/docs/moon/roadmaps/interactive_features.md @@ -0,0 +1,13 @@ +# Interactive Features Roadmap + +Planned interactive/graphics work — see [`docs/moon/ROADMAP.md`](../ROADMAP.md) for the project-level roadmap this rolls up into, and [`docs/moon/research/`](../research/) for the design research behind these items. + +| # | Item | Effort | Status | +| --- | --- | --- | --- | +| IF1 | Offload any heavy rendering/graphics work to a Web Worker via `OffscreenCanvas`, keeping the main thread free for scroll/input | M | 📋 Pending | +| IF2 | WebGL/WebGPU exploration (e.g. Three.js) for a hero or landing-page visual | L | 📋 Pending | +| IF3 | Audio-reactive visualization embedded in the audio signal processing report | M | 📋 Pending | +| IF4 | Interactive route/map visualization for the PCVRP report | M | 📋 Pending | +| IF5 | 360-degree panoramic navigation for a media entry, if a suitable panorama is shot | S | 📋 Pending | + +> **TODO:** Sequence against `user_interface.md` — the persistent-canvas decision there affects how these are implemented. diff --git a/docs/moon/roadmaps/machine_learning.md b/docs/moon/roadmaps/machine_learning.md new file mode 100644 index 0000000..5df0ded --- /dev/null +++ b/docs/moon/roadmaps/machine_learning.md @@ -0,0 +1,11 @@ +# Machine Learning Roadmap + +Planned ML-adjacent content and features — see [`docs/moon/ROADMAP.md`](../ROADMAP.md) for the project-level roadmap this rolls up into, and [`docs/moon/research/`](../research/) for the design research behind these items. + +| # | Item | Effort | Status | +| --- | --- | --- | --- | +| ML1 | Audio signal processing report, backed by a `notebooks/` analysis | M | ✅ Done | +| ML2 | Client-side ("edge") ML inference demo (e.g. ONNX Runtime Web or TensorFlow.js) embedded in a report | L | 📋 Pending | +| ML3 | Evaluate what's feasible entirely client-side given the static export constraint — no server to host a model behind an API | S | 📋 Pending | + +> **TODO:** Pick a first concrete demo (e.g. a small classifier or audio feature extractor) once ML3 is answered. diff --git a/docs/moon/roadmaps/mathematical_optimization.md b/docs/moon/roadmaps/mathematical_optimization.md new file mode 100644 index 0000000..1bac05b --- /dev/null +++ b/docs/moon/roadmaps/mathematical_optimization.md @@ -0,0 +1,11 @@ +# Mathematical Optimization Roadmap + +Planned work embedding operations-research/optimization content into the site — see [`docs/moon/ROADMAP.md`](../ROADMAP.md) for the project-level roadmap this rolls up into, and [`docs/moon/research/`](../research/) for the design research behind these items. + +| # | Item | Effort | Status | +| --- | --- | --- | --- | +| MO1 | PCVRP (Periodic Capacitated Vehicle Routing Problem) report, backed by a `notebooks/` analysis | M | ✅ Done | +| MO2 | In-browser (WebAssembly) demo of a small vehicle-routing solver, embedded directly in the PCVRP report | L | 📋 Pending | +| MO3 | Evaluate a FastAPI + WebSocket backend for optimization workloads too heavy for the client — would require moving that page off the static export | L | 📋 Pending | + +> **TODO:** MO3 is a real architectural fork away from a fully static export — decide whether it's worth a non-static page before scoping it further. diff --git a/docs/moon/roadmaps/user_interface.md b/docs/moon/roadmaps/user_interface.md new file mode 100644 index 0000000..389e1c2 --- /dev/null +++ b/docs/moon/roadmaps/user_interface.md @@ -0,0 +1,13 @@ +# User Interface Roadmap + +Planned work on the site's rendering architecture, layout, and motion — see [`docs/moon/ROADMAP.md`](../ROADMAP.md) for the project-level roadmap this rolls up into, and [`docs/moon/research/`](../research/) for the design research behind these items. + +| # | Item | Effort | Status | +| --- | --- | --- | --- | +| UI1 | Next.js App Router site with Tailwind CSS styling | M | ✅ Done | +| UI2 | Dark/light theme toggle, persisted across navigation via the sidebar | S | ✅ Done | +| UI3 | Evaluate a persistent canvas layer (single WebGL/Three.js context spanning route changes) instead of remounting per page | M | 📋 Pending | +| UI4 | Adopt the View Transitions API + FLIP-style animations for content and page transitions | M | 📋 Pending | +| UI5 | Revisit hydration strategy (islands/partial hydration) if interactive widgets from `interactive_features.md` land | M | 📋 Pending | + +> **TODO:** Reprioritize once `interactive_features.md` items are scoped, since several depend on the rendering architecture decided here first. From 4bb4ad9563953e114737ac32b27592d1f1785fb5 Mon Sep 17 00:00:00 2001 From: ACFHarbinger Date: Sat, 8 Aug 2026 12:07:44 +0100 Subject: [PATCH 11/27] Rewrite infra/ as real self-hosting infra for the static export Replaced the inherited Python+Postgres+Redis backend stack with infra that actually deploys this repo: build the Next.js static export and serve it via nginx. - infra/docker/: Dockerfile now does npm run build then serves out/ via nginx (new nginx.conf); docker-compose.yml/prod.yml drop the db/redis services down to a single `site` service; removed entrypoint.sh (no database to wait for). - infra/k8s/: Deployment/Service/Ingress renamed from tauri-app-template to github-pages, port 8000 -> 80, readiness probe points at `/`; removed the ConfigMap (nothing left to configure via env vars). - infra/helm/: renamed the tauri-app-template/ chart directory to github-pages/ and updated Chart.yaml/values.yaml/templates to match; removed its ConfigMap template too. - infra/terraform/: provisions a real aws_ecr_repository for the built image instead of an empty placeholder; project_name/bucket names updated from tauri-app-template to github-pages. - infra/ansible/: role now installs Docker and runs the built image directly instead of templating a .env file for a Python app. - Every README rewritten to describe what's actually here, with no "not currently used"/"dormant"/"inherited from the template" framing. --- infra/README.md | 13 +++++++ infra/ansible/README.md | 16 +++++++++ infra/ansible/ansible.cfg | 4 +++ infra/ansible/inventory/hosts.ini | 5 +++ infra/ansible/playbook.yml | 8 +++++ infra/ansible/roles/app/tasks/main.yml | 25 +++++++++++++ infra/docker/Dockerfile | 29 +++++++++++++++ infra/docker/README.md | 25 +++++++++++++ infra/docker/docker-compose.prod.yml | 13 +++++++ infra/docker/docker-compose.yml | 13 +++++++ infra/docker/nginx.conf | 14 ++++++++ infra/helm/README.md | 8 +++++ infra/helm/github-pages/Chart.yaml | 6 ++++ .../helm/github-pages/templates/_helpers.tpl | 7 ++++ .../github-pages/templates/deployment.yaml | 24 +++++++++++++ .../helm/github-pages/templates/ingress.yaml | 18 ++++++++++ .../helm/github-pages/templates/service.yaml | 14 ++++++++ infra/helm/github-pages/values.yaml | 23 ++++++++++++ infra/k8s/README.md | 21 +++++++++++ infra/k8s/base/deployment.yaml | 35 +++++++++++++++++++ infra/k8s/base/ingress.yaml | 18 ++++++++++ infra/k8s/base/kustomization.yaml | 7 ++++ infra/k8s/base/service.yaml | 14 ++++++++ infra/k8s/overlays/dev/kustomization.yaml | 19 ++++++++++ infra/k8s/overlays/prod/kustomization.yaml | 25 +++++++++++++ infra/terraform/README.md | 18 ++++++++++ infra/terraform/environments/dev.tfvars | 3 ++ infra/terraform/environments/prod.tfvars | 3 ++ infra/terraform/main.tf | 14 ++++++++ infra/terraform/outputs.tf | 9 +++++ infra/terraform/variables.tf | 17 +++++++++ infra/terraform/versions.tf | 21 +++++++++++ 32 files changed, 489 insertions(+) create mode 100644 infra/README.md create mode 100644 infra/ansible/README.md create mode 100644 infra/ansible/ansible.cfg create mode 100644 infra/ansible/inventory/hosts.ini create mode 100644 infra/ansible/playbook.yml create mode 100644 infra/ansible/roles/app/tasks/main.yml create mode 100644 infra/docker/Dockerfile create mode 100644 infra/docker/README.md create mode 100644 infra/docker/docker-compose.prod.yml create mode 100644 infra/docker/docker-compose.yml create mode 100644 infra/docker/nginx.conf create mode 100644 infra/helm/README.md create mode 100644 infra/helm/github-pages/Chart.yaml create mode 100644 infra/helm/github-pages/templates/_helpers.tpl create mode 100644 infra/helm/github-pages/templates/deployment.yaml create mode 100644 infra/helm/github-pages/templates/ingress.yaml create mode 100644 infra/helm/github-pages/templates/service.yaml create mode 100644 infra/helm/github-pages/values.yaml create mode 100644 infra/k8s/README.md create mode 100644 infra/k8s/base/deployment.yaml create mode 100644 infra/k8s/base/ingress.yaml create mode 100644 infra/k8s/base/kustomization.yaml create mode 100644 infra/k8s/base/service.yaml create mode 100644 infra/k8s/overlays/dev/kustomization.yaml create mode 100644 infra/k8s/overlays/prod/kustomization.yaml create mode 100644 infra/terraform/README.md create mode 100644 infra/terraform/environments/dev.tfvars create mode 100644 infra/terraform/environments/prod.tfvars create mode 100644 infra/terraform/main.tf create mode 100644 infra/terraform/outputs.tf create mode 100644 infra/terraform/variables.tf create mode 100644 infra/terraform/versions.tf diff --git a/infra/README.md b/infra/README.md new file mode 100644 index 0000000..5d67551 --- /dev/null +++ b/infra/README.md @@ -0,0 +1,13 @@ +# infra/ + +Infrastructure-as-code for self-hosting `github-pages` as an alternative to its actual deployment, which is the GitHub Pages static export via [`.github/workflows/deploy.yml`](../.github/workflows/deploy.yml) (also see [`cloud/`](../cloud/) for AWS/Azure static-hosting configs). + +Every option here containerizes the same thing: `npm run build` output, served by nginx. Pick one deployment path — don't run more than one against the same target at once. + +| Directory | What it does | +| --- | --- | +| `docker/` | Build + serve locally via Docker Compose | +| `k8s/` | Kubernetes manifests (base + dev/prod kustomize overlays) for the nginx container | +| `helm/` | Helm chart wrapping the `k8s/` manifests | +| `terraform/` | Cloud provisioning for wherever the container ends up running | +| `ansible/` | Playbook for installing/running the container on a plain host | diff --git a/infra/ansible/README.md b/infra/ansible/README.md new file mode 100644 index 0000000..6e55902 --- /dev/null +++ b/infra/ansible/README.md @@ -0,0 +1,16 @@ +# ansible/ + +Installs Docker on a plain host (VM, bare metal) and runs the container built by `infra/docker/Dockerfile`, as an alternative to the `k8s/`/`helm/` paths when there's no cluster. + +```bash +ansible-playbook -i inventory/hosts.ini playbook.yml +``` + +Requires the `community.docker` collection: `ansible-galaxy collection install community.docker`. + +| Path | Purpose | +| --- | --- | +| `ansible.cfg` | Local Ansible config (inventory path, SSH settings) | +| `inventory/hosts.ini` | Target hosts, grouped | +| `playbook.yml` | Entry-point playbook, applies the `app` role | +| `roles/app/` | Installs Docker, pulls `site_image`, runs it on port 80 | diff --git a/infra/ansible/ansible.cfg b/infra/ansible/ansible.cfg new file mode 100644 index 0000000..bafb9e4 --- /dev/null +++ b/infra/ansible/ansible.cfg @@ -0,0 +1,4 @@ +[defaults] +inventory = inventory/hosts.ini +host_key_checking = False +retry_files_enabled = False diff --git a/infra/ansible/inventory/hosts.ini b/infra/ansible/inventory/hosts.ini new file mode 100644 index 0000000..9f1a302 --- /dev/null +++ b/infra/ansible/inventory/hosts.ini @@ -0,0 +1,5 @@ +[app_servers] +# app1.example.com ansible_user=deploy + +[app_servers:vars] +ansible_python_interpreter=/usr/bin/python3 diff --git a/infra/ansible/playbook.yml b/infra/ansible/playbook.yml new file mode 100644 index 0000000..8c26693 --- /dev/null +++ b/infra/ansible/playbook.yml @@ -0,0 +1,8 @@ +--- +- name: Configure application servers + hosts: app_servers + become: true + vars: + site_image: github-pages:latest + roles: + - app diff --git a/infra/ansible/roles/app/tasks/main.yml b/infra/ansible/roles/app/tasks/main.yml new file mode 100644 index 0000000..2e4b95a --- /dev/null +++ b/infra/ansible/roles/app/tasks/main.yml @@ -0,0 +1,25 @@ +--- +- name: Ensure Docker is installed + ansible.builtin.package: + name: docker.io + state: present + +- name: Ensure Docker is running + ansible.builtin.service: + name: docker + state: started + enabled: true + +- name: Pull the site image + community.docker.docker_image: + name: "{{ site_image }}" + source: pull + +- name: Run the site container + community.docker.docker_container: + name: github-pages + image: "{{ site_image }}" + state: started + restart_policy: always + ports: + - "80:80" diff --git a/infra/docker/Dockerfile b/infra/docker/Dockerfile new file mode 100644 index 0000000..ed19bf3 --- /dev/null +++ b/infra/docker/Dockerfile @@ -0,0 +1,29 @@ +# Builds the Next.js static export and serves it with nginx — a +# self-hosted alternative to the GitHub Pages deployment this repo +# actually uses. +# +# Build context is the repository ROOT (see docker-compose.yml: +# build: { context: .., dockerfile: infra/docker/Dockerfile }). + +# --------------------------------------------------------------------------- +# Stage 1 — build the static export +# --------------------------------------------------------------------------- +FROM node:20-slim AS builder + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +# --------------------------------------------------------------------------- +# Stage 2 — serve it +# --------------------------------------------------------------------------- +FROM nginx:1.27-alpine AS runtime + +COPY infra/docker/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/out /usr/share/nginx/html + +EXPOSE 80 diff --git a/infra/docker/README.md b/infra/docker/README.md new file mode 100644 index 0000000..93ecf3e --- /dev/null +++ b/infra/docker/README.md @@ -0,0 +1,25 @@ +# Docker + +Self-hosted alternative to the GitHub Pages deployment: builds the Next.js static export and serves it with nginx. + +## Quick start + +```bash +docker compose -f infra/docker/docker-compose.yml up --build +``` + +Visit `http://localhost:8080`. + +## Files + +| File | Purpose | +| --- | --- | +| `Dockerfile` | Two-stage build: `npm run build` in a Node image, then serve `out/` with nginx | +| `nginx.conf` | Static-file serving, falling back to the custom `404.html` | +| `docker-compose.yml` | Local stack: the `site` service | +| `docker-compose.prod.yml` | Production overrides (apply with `-f infra/docker/docker-compose.yml -f infra/docker/docker-compose.prod.yml`) | + +## Notes + +- Build context is the **repository root**, not `infra/docker/` — the Dockerfile needs `package.json` and the rest of the site. +- `next.config.js` sets `basePath: '/github-pages'` for the GitHub Pages deployment. Self-hosting at a domain's root will need that removed or made conditional, or every link will expect the `/github-pages` prefix. diff --git a/infra/docker/docker-compose.prod.yml b/infra/docker/docker-compose.prod.yml new file mode 100644 index 0000000..24238a8 --- /dev/null +++ b/infra/docker/docker-compose.prod.yml @@ -0,0 +1,13 @@ +# Production overrides for docker-compose.yml. +# +# Usage: +# docker compose -f infra/docker/docker-compose.yml -f infra/docker/docker-compose.prod.yml up -d + +services: + site: + restart: always + deploy: + resources: + limits: + cpus: "1" + memory: 256M diff --git a/infra/docker/docker-compose.yml b/infra/docker/docker-compose.yml new file mode 100644 index 0000000..1fe8b08 --- /dev/null +++ b/infra/docker/docker-compose.yml @@ -0,0 +1,13 @@ +# Local stack: builds the static export and serves it via nginx. +# +# Usage (from the repo root): +# docker compose -f infra/docker/docker-compose.yml up --build + +services: + site: + build: + context: .. + dockerfile: infra/docker/Dockerfile + ports: + - "8080:80" + restart: unless-stopped diff --git a/infra/docker/nginx.conf b/infra/docker/nginx.conf new file mode 100644 index 0000000..512dd26 --- /dev/null +++ b/infra/docker/nginx.conf @@ -0,0 +1,14 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Next.js static export: try the exact path, then a trailing-slash + # index.html, then fall back to the custom 404 page. + location / { + try_files $uri $uri.html $uri/index.html /404.html; + } + + error_page 404 /404.html; +} diff --git a/infra/helm/README.md b/infra/helm/README.md new file mode 100644 index 0000000..35e1ecb --- /dev/null +++ b/infra/helm/README.md @@ -0,0 +1,8 @@ +# helm/ + +Helm chart packaging the same resources as `infra/k8s/base/`, for anyone who prefers `helm install` over `kubectl apply -k`. Pick one, don't run both against the same cluster/namespace. + +```bash +helm lint infra/helm/github-pages +helm install github-pages infra/helm/github-pages -f infra/helm/github-pages/values.yaml +``` diff --git a/infra/helm/github-pages/Chart.yaml b/infra/helm/github-pages/Chart.yaml new file mode 100644 index 0000000..c186582 --- /dev/null +++ b/infra/helm/github-pages/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: github-pages +description: Helm chart packaging the same Deployment/Service/Ingress as infra/k8s/base. +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/infra/helm/github-pages/templates/_helpers.tpl b/infra/helm/github-pages/templates/_helpers.tpl new file mode 100644 index 0000000..ec805fd --- /dev/null +++ b/infra/helm/github-pages/templates/_helpers.tpl @@ -0,0 +1,7 @@ +{{- define "github-pages.name" -}} +{{- .Chart.Name -}} +{{- end -}} + +{{- define "github-pages.labels" -}} +app: {{ include "github-pages.name" . }} +{{- end -}} diff --git a/infra/helm/github-pages/templates/deployment.yaml b/infra/helm/github-pages/templates/deployment.yaml new file mode 100644 index 0000000..c908c7c --- /dev/null +++ b/infra/helm/github-pages/templates/deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "github-pages.name" . }} + labels: + {{- include "github-pages.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "github-pages.labels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "github-pages.labels" . | nindent 8 }} + spec: + containers: + - name: site + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.service.targetPort }} + resources: + {{- toYaml .Values.resources | nindent 12 }} diff --git a/infra/helm/github-pages/templates/ingress.yaml b/infra/helm/github-pages/templates/ingress.yaml new file mode 100644 index 0000000..ff78479 --- /dev/null +++ b/infra/helm/github-pages/templates/ingress.yaml @@ -0,0 +1,18 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "github-pages.name" . }} +spec: + rules: + - host: {{ .Values.ingress.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "github-pages.name" . }} + port: + number: {{ .Values.service.port }} +{{- end }} diff --git a/infra/helm/github-pages/templates/service.yaml b/infra/helm/github-pages/templates/service.yaml new file mode 100644 index 0000000..9640f86 --- /dev/null +++ b/infra/helm/github-pages/templates/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "github-pages.name" . }} + labels: + {{- include "github-pages.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "github-pages.labels" . | nindent 4 }} + ports: + - name: http + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} diff --git a/infra/helm/github-pages/values.yaml b/infra/helm/github-pages/values.yaml new file mode 100644 index 0000000..e34ca09 --- /dev/null +++ b/infra/helm/github-pages/values.yaml @@ -0,0 +1,23 @@ +replicaCount: 1 + +image: + repository: github-pages + tag: latest + pullPolicy: IfNotPresent + +service: + type: ClusterIP + port: 80 + targetPort: 80 + +ingress: + enabled: false + host: github-pages.example.com + +resources: + requests: + cpu: 50m + memory: 32Mi + limits: + cpu: 200m + memory: 128Mi diff --git a/infra/k8s/README.md b/infra/k8s/README.md new file mode 100644 index 0000000..9d99a41 --- /dev/null +++ b/infra/k8s/README.md @@ -0,0 +1,21 @@ +# k8s/ + +Kustomize-based Kubernetes manifests: a `base/` layer plus per-environment +`overlays/`. Prefer this over hand-maintaining near-duplicate YAML per +environment. + +```bash +kubectl apply -k infra/k8s/overlays/dev +kubectl apply -k infra/k8s/overlays/prod +``` + +| Directory | Purpose | +| --- | --- | +| `base/` | Environment-agnostic Deployment/Service/Ingress for the nginx container built by `infra/docker/Dockerfile` | +| `overlays/dev/` | Dev patches: fewer replicas, dev image tag | +| `overlays/prod/` | Prod patches: replica count, resource limits | + +> **TODO:** Point the `image:` field in `base/deployment.yaml` at your real +> container registry once one exists. The Helm chart in `infra/helm/` +> packages the same base manifests for teams that prefer `helm install` +> over `kubectl apply -k`. diff --git a/infra/k8s/base/deployment.yaml b/infra/k8s/base/deployment.yaml new file mode 100644 index 0000000..1656e84 --- /dev/null +++ b/infra/k8s/base/deployment.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: github-pages + labels: + app: github-pages +spec: + replicas: 1 + selector: + matchLabels: + app: github-pages + template: + metadata: + labels: + app: github-pages + spec: + containers: + - name: site + # TODO: point at your real registry once infra/docker/Dockerfile is published there. + image: github-pages:latest + ports: + - containerPort: 80 + readinessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + requests: + cpu: "50m" + memory: "32Mi" + limits: + cpu: "200m" + memory: "128Mi" diff --git a/infra/k8s/base/ingress.yaml b/infra/k8s/base/ingress.yaml new file mode 100644 index 0000000..5718211 --- /dev/null +++ b/infra/k8s/base/ingress.yaml @@ -0,0 +1,18 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: github-pages + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + rules: + - host: github-pages.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: github-pages + port: + number: 80 diff --git a/infra/k8s/base/kustomization.yaml b/infra/k8s/base/kustomization.yaml new file mode 100644 index 0000000..1d4f62e --- /dev/null +++ b/infra/k8s/base/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deployment.yaml + - service.yaml + - ingress.yaml diff --git a/infra/k8s/base/service.yaml b/infra/k8s/base/service.yaml new file mode 100644 index 0000000..963d1b4 --- /dev/null +++ b/infra/k8s/base/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: github-pages + labels: + app: github-pages +spec: + selector: + app: github-pages + ports: + - name: http + port: 80 + targetPort: 80 + type: ClusterIP diff --git a/infra/k8s/overlays/dev/kustomization.yaml b/infra/k8s/overlays/dev/kustomization.yaml new file mode 100644 index 0000000..e241b6c --- /dev/null +++ b/infra/k8s/overlays/dev/kustomization.yaml @@ -0,0 +1,19 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: github-pages-dev + +resources: + - ../../base + +patches: + - target: + kind: Deployment + name: github-pages + patch: |- + - op: replace + path: /spec/replicas + value: 1 + - op: replace + path: /spec/template/spec/containers/0/image + value: github-pages:dev diff --git a/infra/k8s/overlays/prod/kustomization.yaml b/infra/k8s/overlays/prod/kustomization.yaml new file mode 100644 index 0000000..07ca34b --- /dev/null +++ b/infra/k8s/overlays/prod/kustomization.yaml @@ -0,0 +1,25 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: github-pages-prod + +resources: + - ../../base + +patches: + - target: + kind: Deployment + name: github-pages + patch: |- + - op: replace + path: /spec/replicas + value: 3 + - op: replace + path: /spec/template/spec/containers/0/image + value: github-pages:latest + - op: replace + path: /spec/template/spec/containers/0/resources/limits/cpu + value: "500m" + - op: replace + path: /spec/template/spec/containers/0/resources/limits/memory + value: "256Mi" diff --git a/infra/terraform/README.md b/infra/terraform/README.md new file mode 100644 index 0000000..0357cc6 --- /dev/null +++ b/infra/terraform/README.md @@ -0,0 +1,18 @@ +# terraform/ + +Provisions an AWS ECR repository to push the image built by `infra/docker/Dockerfile` to, for the `k8s/`/`helm/`/`ansible/` deployment paths to pull from. + +```bash +cd infra/terraform +terraform init +terraform plan -var-file=environments/dev.tfvars +terraform apply -var-file=environments/dev.tfvars +``` + +| File | Purpose | +| --- | --- | +| `versions.tf` | Required Terraform + AWS provider versions, remote state backend (commented, fill in before first `init`) | +| `variables.tf` | Input variables | +| `main.tf` | The `aws_ecr_repository` resource | +| `outputs.tf` | `ecr_repository_url` — push the built image here | +| `environments/*.tfvars` | Per-environment variable values | diff --git a/infra/terraform/environments/dev.tfvars b/infra/terraform/environments/dev.tfvars new file mode 100644 index 0000000..e6808bd --- /dev/null +++ b/infra/terraform/environments/dev.tfvars @@ -0,0 +1,3 @@ +environment = "dev" +project_name = "github-pages" +region = "us-east-1" diff --git a/infra/terraform/environments/prod.tfvars b/infra/terraform/environments/prod.tfvars new file mode 100644 index 0000000..e19b23f --- /dev/null +++ b/infra/terraform/environments/prod.tfvars @@ -0,0 +1,3 @@ +environment = "prod" +project_name = "github-pages" +region = "us-east-1" diff --git a/infra/terraform/main.tf b/infra/terraform/main.tf new file mode 100644 index 0000000..67a59a3 --- /dev/null +++ b/infra/terraform/main.tf @@ -0,0 +1,14 @@ +locals { + name_prefix = "${var.project_name}-${var.environment}" +} + +# Container registry to push infra/docker/Dockerfile's built image to, +# for the k8s/helm/ansible deployment paths to pull from. +resource "aws_ecr_repository" "site" { + name = local.name_prefix + image_tag_mutability = "MUTABLE" + + image_scanning_configuration { + scan_on_push = true + } +} diff --git a/infra/terraform/outputs.tf b/infra/terraform/outputs.tf new file mode 100644 index 0000000..cad2bca --- /dev/null +++ b/infra/terraform/outputs.tf @@ -0,0 +1,9 @@ +output "name_prefix" { + description = "Resource name prefix in use for this environment." + value = local.name_prefix +} + +output "ecr_repository_url" { + description = "Push the image built by infra/docker/Dockerfile here." + value = aws_ecr_repository.site.repository_url +} diff --git a/infra/terraform/variables.tf b/infra/terraform/variables.tf new file mode 100644 index 0000000..dc65107 --- /dev/null +++ b/infra/terraform/variables.tf @@ -0,0 +1,17 @@ +variable "environment" { + description = "Deployment environment name (dev, staging, prod)." + type = string + default = "dev" +} + +variable "project_name" { + description = "Short name used to prefix provisioned resources." + type = string + default = "github-pages" +} + +variable "region" { + description = "Cloud provider region." + type = string + default = "us-east-1" +} diff --git a/infra/terraform/versions.tf b/infra/terraform/versions.tf new file mode 100644 index 0000000..97c4a93 --- /dev/null +++ b/infra/terraform/versions.tf @@ -0,0 +1,21 @@ +terraform { + required_version = ">= 1.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + + # TODO: configure a remote state backend before using this in a team setting. + # backend "s3" { + # bucket = "github-pages-tfstate" + # key = "github-pages/terraform.tfstate" + # region = "us-east-1" + # } +} + +provider "aws" { + region = var.region +} From cd805359c7ad5d1661faa76141dd64f072a1f6fe Mon Sep 17 00:00:00 2001 From: ACFHarbinger Date: Sat, 8 Aug 2026 12:21:23 +0100 Subject: [PATCH 12/27] Replace Jest with Vitest and move unit tests to test/unit/ - Add vitest, @vitejs/plugin-react, jsdom, msw, @testing-library/user-event; remove jest, jest-environment-jsdom, ts-jest, @types/jest. - vitest.config.ts: jsdom environment, @/* alias matching tsconfig, globals enabled, CSS processing disabled (the repo's postcss.config.js uses Next.js-style string plugin names Vite's loader can't resolve). - test/vitest.setup.ts: jest-dom matchers plus MSW server lifecycle (see the next commit for the integration tests that use it). - Move src/components/__tests__/*.test.tsx to test/unit/components/, mirroring the src/components/{layout,ui}/ layout, converting jest.fn()/jest.mock() to vi.fn()/vi.mock(). - package.json: test -> vitest run, test:watch -> vitest, plus new test:unit/test:integration scripts. All 9 suites (25 tests) pass under `npm run test:unit`. --- jest.config.js | 22 - jest.setup.js | 1 - package-lock.json | 6071 +++++++---------- package.json | 22 +- test/integration/mocks/handlers.ts | 19 + test/integration/mocks/server.ts | 10 + .../unit/components}/PostWrapper.test.tsx | 4 +- .../unit/components}/ReportWrapper.test.tsx | 4 +- .../unit/components/layout}/Footer.test.tsx | 2 +- .../unit/components/layout}/Header.test.tsx | 4 +- .../unit/components/layout}/Sidebar.test.tsx | 16 +- .../components/layout}/SidebarItem.test.tsx | 0 .../unit/components/ui}/Badge.test.tsx | 0 .../unit/components/ui}/GlassCard.test.tsx | 0 .../components/ui}/SectionHeading.test.tsx | 0 test/vitest.setup.ts | 7 + vitest.config.ts | 27 + 17 files changed, 2575 insertions(+), 3634 deletions(-) delete mode 100644 jest.config.js delete mode 100644 jest.setup.js create mode 100644 test/integration/mocks/handlers.ts create mode 100644 test/integration/mocks/server.ts rename {src/components/__tests__ => test/unit/components}/PostWrapper.test.tsx (91%) rename {src/components/__tests__ => test/unit/components}/ReportWrapper.test.tsx (95%) rename {src/components/__tests__ => test/unit/components/layout}/Footer.test.tsx (95%) rename {src/components/__tests__ => test/unit/components/layout}/Header.test.tsx (92%) rename {src/components/__tests__ => test/unit/components/layout}/Sidebar.test.tsx (85%) rename {src/components/__tests__ => test/unit/components/layout}/SidebarItem.test.tsx (100%) rename {src/components/__tests__ => test/unit/components/ui}/Badge.test.tsx (100%) rename {src/components/__tests__ => test/unit/components/ui}/GlassCard.test.tsx (100%) rename {src/components/__tests__ => test/unit/components/ui}/SectionHeading.test.tsx (100%) create mode 100644 test/vitest.setup.ts create mode 100644 vitest.config.ts diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index c445371..0000000 --- a/jest.config.js +++ /dev/null @@ -1,22 +0,0 @@ -const nextJest = require('next/jest') - -const createJestConfig = nextJest({ - // Provide the path to your Next.js app to load next.config.js and .env files in your test environment - dir: './', -}) - -// Add any custom config to be passed to Jest -const customJestConfig = { - setupFilesAfterEnv: ['/jest.setup.js'], - testEnvironment: 'jest-environment-jsdom', - moduleNameMapper: { - // Handle module aliases (this will be automatically configured for you soon) - // Handle module aliases (this will be automatically configured for you soon) - '^@/components/(.*)$': '/src/components/$1', - '^@/pages/(.*)$': '/src/pages/$1', - '^@/(.*)$': '/$1', - }, -} - -// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async -module.exports = createJestConfig(customJestConfig) diff --git a/jest.setup.js b/jest.setup.js deleted file mode 100644 index c44951a..0000000 --- a/jest.setup.js +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom' diff --git a/package-lock.json b/package-lock.json index cb7fd37..f46b44b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,20 +21,21 @@ "@next/eslint-plugin-next": "^14.2.3", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", - "@types/jest": "^30.0.0", + "@testing-library/user-event": "^14.5.2", "@types/node": "^20.12.12", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", "autoprefixer": "^10.4.19", "cypress": "^15.8.2", "eslint": "^8.57.0", "eslint-config-next": "^14.2.3", - "jest": "^30.2.0", - "jest-environment-jsdom": "^30.2.0", + "jsdom": "^25.0.0", + "msw": "^2.4.9", "postcss": "^8.4.38", "tailwindcss": "^3.4.3", - "ts-jest": "^29.4.6", - "typescript": "^5.4.5" + "typescript": "^5.4.5", + "vitest": "^2.1.1" } }, "node_modules/@adobe/css-tools": { @@ -212,9 +213,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "engines": { @@ -281,221 +282,14 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -504,14 +298,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -578,13 +372,6 @@ "node": ">=6.9.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -810,712 +597,674 @@ "tslib": "^2.4.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, + "optional": true, + "os": [ + "aix" + ], "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": ">=12" } }, - "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==", + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=12" } }, - "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==", + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=12" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=12" } }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=12" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10.10.0" + "node": ">=12" } }, - "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==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=12" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": ">=12" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@jest/console": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", - "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12" } }, - "node_modules/@jest/core": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", - "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/console": "30.2.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.2.0", - "jest-config": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-resolve-dependencies": "30.2.0", - "jest-runner": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "jest-watcher": "30.2.0", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">=12" } }, - "node_modules/@jest/core/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==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=12" } }, - "node_modules/@jest/core/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12" } }, - "node_modules/@jest/core/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12" } }, - "node_modules/@jest/environment": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", - "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12" } }, - "node_modules/@jest/environment-jsdom-abstract": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.2.0.tgz", - "integrity": "sha512-kazxw2L9IPuZpQ0mEt9lu9Z98SqR74xcagANmMBU16X0lS23yPc0+S6hGLUz8kVRlomZEs/5S/Zlpqwf5yu6OQ==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/jsdom": "^21.1.7", - "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "canvas": "^3.0.0", - "jsdom": "*" + "funding": { + "url": "https://opencollective.com/eslint" }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@jest/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "30.2.0", - "jest-snapshot": "30.2.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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@jest/expect-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", - "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@jest/fake-timers": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", - "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", - "@sinonjs/fake-timers": "^13.0.0", - "@types/node": "*", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "license": "MIT", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@jest/globals": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", - "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/types": "30.2.0", - "jest-mock": "30.2.0" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10.10.0" } }, - "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "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": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.0.1" + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@inquirer/ansi": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.7.tgz", + "integrity": "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==", + "dev": true, + "license": "MIT", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" } }, - "node_modules/@jest/reporters": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", - "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", + "node_modules/@inquirer/confirm": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.1.tgz", + "integrity": "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==", "dev": true, "license": "MIT", "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "@types/node": ">=18" }, "peerDependenciesMeta": { - "node-notifier": { + "@types/node": { "optional": true } } }, - "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/@inquirer/core": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.1.tgz", + "integrity": "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/reporters/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependencies": { + "@types/node": ">=18" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@jest/reporters/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "node_modules/@inquirer/figures": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.7.tgz", + "integrity": "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==", "dev": true, "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" } }, - "node_modules/@jest/snapshot-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", - "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "node_modules/@inquirer/type": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.7.tgz", + "integrity": "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@jest/test-result": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", - "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jest/console": "30.2.0", - "@jest/types": "30.2.0", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12" } }, - "node_modules/@jest/test-sequencer": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", - "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/test-result": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "slash": "^3.0.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@jest/transform": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", - "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.2.0", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "micromatch": "^4.0.8", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/types": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", - "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "ansi-regex": "^6.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/@jridgewell/gen-mapping": { @@ -1568,6 +1317,48 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mswjs/interceptors": { + "version": "0.41.9", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.9.tgz", + "integrity": "sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@mswjs/interceptors/node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -1838,6 +1629,31 @@ "node": ">=12.4.0" } }, + "node_modules/@open-draft/deferred-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-3.0.0.tgz", + "integrity": "sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1849,59 +1665,376 @@ "node": ">=14" } }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", "dev": true, "license": "MIT" }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz", - "integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@sinclair/typebox": { - "version": "0.34.47", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.47.tgz", - "integrity": "sha512-ZGIBQ+XDvO5JQku9wmwtabcVTHJsgSWAHYtVuM9pBNNR5E88v6Jcj/llpmsjivig5X8A8HHOb4/mbEKPS5EvAw==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz", + "integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==", + "dev": true, + "license": "MIT" }, "node_modules/@swc/counter": { "version": "0.1.3", @@ -1915,6 +2048,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -1994,6 +2128,20 @@ } } }, + "node_modules/@testing-library/user-event": { + "version": "14.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.3.tgz", + "integrity": "sha512-6dBq67jT8lE+JTE8Exm02Kt6ze43hz1jdiSpSJwtTZiT1xQQ6b7nZYTTQ9njdArdU8XklOwaDp/AbT/eYSKF4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -2066,6 +2214,13 @@ "@types/ms": "*" } }, + "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/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -2075,91 +2230,6 @@ "@types/unist": "*" } }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^30.0.0", - "pretty-format": "^30.0.0" - } - }, - "node_modules/@types/jest/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@types/jest/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@types/jest/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/jsdom": { - "version": "21.1.7", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", - "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -2222,6 +2292,16 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/set-cookie-parser": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.10.tgz", + "integrity": "sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", @@ -2236,10 +2316,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "node_modules/@types/statuses": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", "dev": true, "license": "MIT" }, @@ -2250,36 +2330,12 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -2850,6 +2906,140 @@ "win32" ] }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@zeit/schemas": { "version": "2.36.0", "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", @@ -3237,6 +3427,16 @@ "node": ">=0.8" } }, + "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/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -3372,105 +3572,6 @@ "node": ">= 0.4" } }, - "node_modules/babel-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", - "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "30.2.0", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", - "dev": true, - "license": "BSD-3-Clause", - "workspaces": [ - "test/babel-8" - ], - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", - "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/babel__core": "^7.20.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", - "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" - } - }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -3703,29 +3804,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -3761,13 +3839,6 @@ "node": "*" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -3788,6 +3859,16 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cachedir": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", @@ -3917,6 +3998,23 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3948,16 +4046,6 @@ "url": "https://github.com/chalk/chalk-template?sponsor=1" } }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -3988,6 +4076,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -4029,13 +4127,6 @@ "node": ">=8" } }, - "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", - "dev": true, - "license": "MIT" - }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -4104,6 +4195,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -4160,24 +4261,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "dev": true, - "license": "MIT" - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4324,6 +4407,20 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -4642,19 +4739,14 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/dedent": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", - "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } + "engines": { + "node": ">=6" } }, "node_modules/deep-extend": { @@ -4673,16 +4765,6 @@ "dev": true, "license": "MIT" }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -4738,16 +4820,6 @@ "node": ">=6" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/devlop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", @@ -4834,19 +4906,6 @@ "dev": true, "license": "ISC" }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -4891,16 +4950,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-abstract": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", @@ -5018,6 +5067,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -5078,6 +5134,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -5609,6 +5704,16 @@ "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", @@ -5662,32 +5767,14 @@ "node": ">=4" } }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "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": "MIT", - "dependencies": { - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" - }, + "license": "Apache-2.0", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12.0.0" } }, "node_modules/extend": { @@ -5792,6 +5879,33 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -5802,16 +5916,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -5874,20 +5978,6 @@ "node": ">=8" } }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -6120,936 +6210,9 @@ "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/global-dirs/node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "license": "MIT", - "dependencies": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/gray-matter/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasha/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hast-util-sanitize": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz", - "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "unist-util-position": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-signature": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", - "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.18.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bun-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", - "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.7.1" - } - }, - "node_modules/is-bun-module/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -7058,29 +6221,25 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "license": "MIT", - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, "engines": { "node": ">=10" }, @@ -7088,12 +6247,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, "engines": { "node": ">= 0.4" }, @@ -7101,98 +6265,115 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "resolve-pkg-maps": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.12.0" + "dependencies": { + "assert-plus": "^1.0.0" } }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">= 0.4" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, "license": "MIT", + "dependencies": { + "ini": "2.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-port-reachable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", - "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "node_modules/global-dirs/node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -7201,10 +6382,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", "engines": { @@ -7214,62 +6395,72 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/graphql": { + "version": "16.14.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", + "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", "dev": true, "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "license": "MIT", "dependencies": { - "call-bound": "^1.0.3" + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6.0" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "sprintf-js": "~1.0.2" } }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, "engines": { "node": ">= 0.4" }, @@ -7277,48 +6468,37 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "es-define-property": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -7326,15 +6506,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, "engines": { "node": ">= 0.4" }, @@ -7342,15 +6519,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -7359,1240 +6535,939 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, "license": "MIT", "dependencies": { - "is-docker": "^2.0.0" + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "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==", - "license": "ISC" - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, - "license": "BSD-3-Clause", + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=8" } }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/hast-util-sanitize": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz", + "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "unist-util-position": "^5.0.0" }, - "engines": { - "node": ">=10" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" }, - "engines": { - "node": ">=10" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" + "@types/hast": "^3.0.0" }, - "engines": { - "node": ">=10" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "node_modules/headers-polyfill": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-5.0.1.tgz", + "integrity": "sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" + "@types/set-cookie-parser": "^2.4.10", + "set-cookie-parser": "^3.0.1" } }, - "node_modules/iterator.prototype": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", - "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.1.4", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "get-proto": "^1.0.0", - "has-symbols": "^1.1.0", - "set-function-name": "^2.0.2" + "whatwg-encoding": "^3.1.1" }, "engines": { - "node": ">= 0.4" + "node": ">=18" } }, - "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", - "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@jest/core": "30.2.0", - "@jest/types": "30.2.0", - "import-local": "^3.2.0", - "jest-cli": "30.2.0" - }, - "bin": { - "jest": "bin/jest.js" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">= 14" } }, - "node_modules/jest-changed-files": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", - "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", + "node_modules/http-signature": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.2.0", - "p-limit": "^3.1.0" + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.18.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10" } }, - "node_modules/jest-circus": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", - "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "p-limit": "^3.1.0", - "pretty-format": "30.2.0", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 14" } }, - "node_modules/jest-circus/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", + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=10.17.0" } }, - "node_modules/jest-circus/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-circus/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, - "license": "MIT" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" }, - "node_modules/jest-cli": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", - "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", + "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", - "dependencies": { - "@jest/core": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">= 4" } }, - "node_modules/jest-config": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", - "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.2.0", - "@jest/types": "30.2.0", - "babel-jest": "30.2.0", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-circus": "30.2.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-runner": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "micromatch": "^4.0.8", - "parse-json": "^5.2.0", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" + "node": ">=6" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-config/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==", + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=4" } }, - "node_modules/jest-config/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "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", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">=0.8.19" } }, - "node_modules/jest-config/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "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": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/jest-config/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/jest-config/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, - "license": "ISC", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 0.4" } }, - "node_modules/jest-config/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-config/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-diff": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.2.0" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-diff/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==", + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-diff/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "binary-extensions": "^2.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-diff/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "license": "MIT", "dependencies": { - "detect-newline": "^3.1.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-each": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", - "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "jest-util": "30.2.0", - "pretty-format": "30.2.0" + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" } }, - "node_modules/jest-each/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==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-each/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "hasown": "^2.0.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-each/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-environment-jsdom": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.2.0.tgz", - "integrity": "sha512-zbBTiqr2Vl78pKp/laGBREYzbZx9ZtqPjOK4++lL4BNDhxRnahg51HtoDrk9/VjIy9IthNEWdKVd7H5bqBhiWQ==", + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/environment-jsdom-abstract": "30.2.0", - "@types/jsdom": "^21.1.7", - "@types/node": "*", - "jsdom": "^26.1.0" + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "canvas": "^3.0.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-environment-node": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", - "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-haste-map": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", - "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", - "dev": true, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "micromatch": "^4.0.8", - "walker": "^1.0.8" + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" }, - "optionalDependencies": { - "fsevents": "^2.3.3" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-leak-detector": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", - "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", - "dev": true, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.2.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-leak-detector/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==", + "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": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=0.10.0" } }, - "node_modules/jest-leak-detector/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "call-bound": "^1.0.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-leak-detector/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-matcher-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", - "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", - "dev": true, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.2.0", - "pretty-format": "30.2.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-matcher-utils/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==", + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "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": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "is-extglob": "^2.1.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-matcher-utils/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-message-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", - "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.2.0", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-message-util/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==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", "dev": true, "license": "MIT" }, - "node_modules/jest-mock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-util": "30.2.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.12.0" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, - "peerDependencies": { - "jest-resolve": "*" + "engines": { + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-resolve": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", - "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", - "dev": true, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", - "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.2.0" + "node": ">=12" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-runner": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", - "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", - "dev": true, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", "license": "MIT", - "dependencies": { - "@jest/console": "30.2.0", - "@jest/environment": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-leak-detector": "30.2.0", - "jest-message-util": "30.2.0", - "jest-resolve": "30.2.0", - "jest-runtime": "30.2.0", - "jest-util": "30.2.0", - "jest-watcher": "30.2.0", - "jest-worker": "30.2.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", - "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/globals": "30.2.0", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "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", - "dependencies": { - "balanced-match": "^1.0.0" - } + "license": "MIT" }, - "node_modules/jest-runtime/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-runtime/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-runtime/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "call-bound": "^1.0.3" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-runtime/node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-snapshot": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", - "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0", - "chalk": "^4.1.2", - "expect": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-diff": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "pretty-format": "30.2.0", - "semver": "^7.7.2", - "synckit": "^0.11.8" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-snapshot/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==", + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "which-typed-array": "^1.1.16" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-snapshot/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true, "license": "MIT" }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", "engines": { "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", - "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-validate": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", - "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.2.0" + "call-bound": "^1.0.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-validate/node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", - "dev": true, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "is-docker": "^2.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-validate/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, "license": "MIT" }, - "node_modules/jest-watcher": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", - "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.2.0", - "string-length": "^4.0.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "license": "MIT" }, - "node_modules/jest-worker": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", - "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.2.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "has-flag": "^4.0.0" + "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jiti": { @@ -8633,31 +7508,31 @@ "license": "MIT" }, "node_modules/jsdom": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", - "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "cssstyle": "^4.2.1", + "cssstyle": "^4.1.0", "data-urls": "^5.0.0", - "decimal.js": "^10.5.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", + "https-proxy-agent": "^7.0.5", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.16", - "parse5": "^7.2.1", - "rrweb-cssom": "^0.8.0", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^5.1.1", + "tough-cookie": "^5.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.1.1", + "whatwg-url": "^14.0.0", "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, @@ -8665,7 +7540,7 @@ "node": ">=18" }, "peerDependencies": { - "canvas": "^3.0.0" + "canvas": "^2.11.2" }, "peerDependenciesMeta": { "canvas": { @@ -8673,6 +7548,13 @@ } } }, + "node_modules/jsdom/node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -8693,13 +7575,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -8825,16 +7700,6 @@ "node": ">=0.10" } }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -8902,19 +7767,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -8922,13 +7774,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -9034,6 +7879,13 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -9048,65 +7900,29 @@ "version": "0.383.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.383.0.tgz", "integrity": "sha512-13xlG0CQCJtzjSQYwwJ3WRqMHtRj3EXmLlorrARt7y+IHnxUCp3XyFNL1DfaGySWxHObDvnu1u1dV+0VMKHUSg==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, - "license": "MIT", - "bin": { - "lz-string": "bin/bin.js" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "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": "ISC", + "license": "MIT", "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "lz-string": "bin/bin.js" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "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": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "tmpl": "1.0.5" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/math-intrinsics": { @@ -9770,6 +8586,117 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/msw": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.15.0.tgz", + "integrity": "sha512-2wQAmKkQKxRuXvYJxVhPGG0wZNBQyD06oJvxqw90XqLvptdqxdlHrFUfEteKkpaNORX3Xzc+HtEl/q0nfmN2wQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@inquirer/confirm": "^6.0.11", + "@mswjs/interceptors": "^0.41.3", + "@open-draft/deferred-promise": "^3.0.0", + "@types/statuses": "^2.0.6", + "cookie": "^1.1.1", + "graphql": "^16.13.2", + "headers-polyfill": "^5.0.1", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "rettime": "^0.11.11", + "statuses": "^2.0.2", + "strict-event-emitter": "^0.5.1", + "tough-cookie": "^6.0.1", + "type-fest": "^5.5.0", + "until-async": "^3.0.2", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/msw/node_modules/tldts": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.10" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/msw/node_modules/tldts-core": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", + "dev": true, + "license": "MIT" + }, + "node_modules/msw/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/msw/node_modules/type-fest": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz", + "integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mute-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -9832,13 +8759,6 @@ "node": ">= 0.6" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, "node_modules/next": { "version": "14.2.33", "resolved": "https://registry.npmjs.org/next/-/next-14.2.33.tgz", @@ -9927,13 +8847,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -10172,6 +9085,13 @@ "dev": true, "license": "MIT" }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -10206,35 +9126,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", @@ -10251,23 +9142,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -10281,25 +9155,6 @@ "node": ">=6" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -10385,6 +9240,23 @@ "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", "license": "MIT" }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -10438,19 +9310,6 @@ "node": ">= 6" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -10699,23 +9558,6 @@ "node": ">=6" } }, - "node_modules/pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, "node_modules/qs": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", @@ -10820,6 +9662,16 @@ "dev": true, "license": "MIT" }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -11037,29 +9889,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -11084,6 +9913,13 @@ "node": ">=8" } }, + "node_modules/rettime": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.11.11.tgz", + "integrity": "sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -11119,6 +9955,52 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, "node_modules/rrweb-cssom": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", @@ -11370,6 +10252,13 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/set-cookie-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz", + "integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==", + "dev": true, + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -11516,22 +10405,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", @@ -11547,16 +10433,6 @@ "node": ">=8" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -11566,17 +10442,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/space-separated-tokens": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", @@ -11626,29 +10491,30 @@ "dev": true, "license": "MIT" }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "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", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } + "license": "MIT" }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -11671,19 +10537,12 @@ "node": ">=10.0.0" } }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } + "license": "MIT" }, "node_modules/string-width": { "version": "4.2.3", @@ -12023,22 +10882,6 @@ "dev": true, "license": "MIT" }, - "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.9" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, "node_modules/systeminformation": { "version": "5.30.2", "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.30.2.tgz", @@ -12066,6 +10909,19 @@ "url": "https://www.buymeacoffee.com/systeminfo" } }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tailwindcss": { "version": "3.4.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", @@ -12173,21 +11029,6 @@ } } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -12235,6 +11076,20 @@ "dev": true, "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -12284,6 +11139,36 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tldts": { "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", @@ -12314,13 +11199,6 @@ "node": ">=14.14" } }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -12410,85 +11288,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/ts-jest": { - "version": "29.4.6", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", - "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.8", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.3", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0 || ^30.0.0", - "@jest/types": "^29.0.0 || ^30.0.0", - "babel-jest": "^29.0.0 || ^30.0.0", - "jest": "^29.0.0 || ^30.0.0", - "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jest-util": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -12554,16 +11353,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -12670,20 +11459,6 @@ "node": ">=14.17" } }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -12842,6 +11617,16 @@ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, + "node_modules/until-async": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", + "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/kettanaito" + } + }, "node_modules/untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", @@ -12919,21 +11704,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -12986,6 +11756,156 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -12999,16 +11919,6 @@ "node": ">=18" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -13161,6 +12071,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/widest-line": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", @@ -13230,13 +12157,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -13336,33 +12256,6 @@ "dev": true, "license": "ISC" }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/ws": { "version": "8.19.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", diff --git a/package.json b/package.json index 2db896f..b97a43a 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,13 @@ "start": "npx serve@latest out", "lint": "next lint", "deploy": "next build && touch out/.nojekyll", - "test": "jest", - "test:watch": "jest --watch" + "test": "vitest run", + "test:watch": "vitest", + "test:unit": "vitest run test/unit", + "test:integration": "vitest run test/integration", + "cypress:open": "cypress open --config-file test/cypress/cypress.config.js", + "cypress:run": "cypress run --config-file test/cypress/cypress.config.js", + "cypress:smoke": "cypress run --config-file test/cypress/cypress.config.js --spec \"test/cypress/smoke/**/*.cy.js\"" }, "homepage": "https://acfharbinger.github.io/github-pages", "dependencies": { @@ -35,19 +40,20 @@ "@next/eslint-plugin-next": "^14.2.3", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", - "@types/jest": "^30.0.0", + "@testing-library/user-event": "^14.5.2", "@types/node": "^20.12.12", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", "autoprefixer": "^10.4.19", "cypress": "^15.8.2", "eslint": "^8.57.0", "eslint-config-next": "^14.2.3", - "jest": "^30.2.0", - "jest-environment-jsdom": "^30.2.0", + "jsdom": "^25.0.0", + "msw": "^2.4.9", "postcss": "^8.4.38", "tailwindcss": "^3.4.3", - "ts-jest": "^29.4.6", - "typescript": "^5.4.5" + "typescript": "^5.4.5", + "vitest": "^2.1.1" } -} \ No newline at end of file +} diff --git a/test/integration/mocks/handlers.ts b/test/integration/mocks/handlers.ts new file mode 100644 index 0000000..51d19a0 --- /dev/null +++ b/test/integration/mocks/handlers.ts @@ -0,0 +1,19 @@ +import { http, HttpResponse } from 'msw'; + +/** + * Example handler for the GitHub profile linked from the Sidebar + * (https://github.com/acfharbinger). Not currently fetched by the app — + * this documents the pattern integration tests should follow if a future + * feature (e.g. live follower/stats data) adds a real network call, and + * gives the MSW server at least one realistic handler to exercise. + */ +export const handlers = [ + http.get('https://api.github.com/users/acfharbinger', () => { + return HttpResponse.json({ + login: 'acfharbinger', + name: 'ACFHarbinger', + public_repos: 42, + followers: 7, + }); + }), +]; diff --git a/test/integration/mocks/server.ts b/test/integration/mocks/server.ts new file mode 100644 index 0000000..cb2da3f --- /dev/null +++ b/test/integration/mocks/server.ts @@ -0,0 +1,10 @@ +import { setupServer } from 'msw/node'; +import { handlers } from './handlers'; + +/** + * Node-side MSW server for integration tests. Started/stopped in + * test/vitest.setup.ts. `onUnhandledRequest: 'error'` means any test that + * accidentally reaches the real network fails loudly instead of silently + * hitting a live endpoint. + */ +export const server = setupServer(...handlers); diff --git a/src/components/__tests__/PostWrapper.test.tsx b/test/unit/components/PostWrapper.test.tsx similarity index 91% rename from src/components/__tests__/PostWrapper.test.tsx rename to test/unit/components/PostWrapper.test.tsx index a2e95b6..2a7fb70 100644 --- a/src/components/__tests__/PostWrapper.test.tsx +++ b/test/unit/components/PostWrapper.test.tsx @@ -15,8 +15,8 @@ describe('PostWrapper', () => { render(); // The highlighting happens in useEffect, so we wait. - // However, our simple mock content might not trigger complex highlighting classes - // without the full logic running on specific tokens. + // However, our simple mock content might not trigger complex highlighting classes + // without the full logic running on specific tokens. // We can at least verify the container exists and text is present. await waitFor(() => { expect(screen.getByText(/def/)).toBeInTheDocument(); diff --git a/src/components/__tests__/ReportWrapper.test.tsx b/test/unit/components/ReportWrapper.test.tsx similarity index 95% rename from src/components/__tests__/ReportWrapper.test.tsx rename to test/unit/components/ReportWrapper.test.tsx index ab1e74c..117b4cc 100644 --- a/src/components/__tests__/ReportWrapper.test.tsx +++ b/test/unit/components/ReportWrapper.test.tsx @@ -7,8 +7,8 @@ beforeAll(() => { window.MathJax = { tex: { inlineMath: [], displayMath: [] }, svg: { fontCache: '' }, - typeset: jest.fn(), - typesetClear: jest.fn(), + typeset: vi.fn(), + typesetClear: vi.fn(), }; }); diff --git a/src/components/__tests__/Footer.test.tsx b/test/unit/components/layout/Footer.test.tsx similarity index 95% rename from src/components/__tests__/Footer.test.tsx rename to test/unit/components/layout/Footer.test.tsx index ce0eb26..1ac4e7c 100644 --- a/src/components/__tests__/Footer.test.tsx +++ b/test/unit/components/layout/Footer.test.tsx @@ -3,7 +3,7 @@ import { render, screen, fireEvent } from '@testing-library/react'; import Footer from '@/src/components/layout/Footer'; describe('Footer', () => { - const mockToggleTheme = jest.fn(); + const mockToggleTheme = vi.fn(); it('renders copyright and links', () => { render(