diff --git a/.codex/agents/blackbox-overview-docs.toml b/.codex/agents/blackbox-overview-docs.toml new file mode 100644 index 0000000..e0871f8 --- /dev/null +++ b/.codex/agents/blackbox-overview-docs.toml @@ -0,0 +1,65 @@ +name = "blackbox_overview_docs" +description = "Suites Blackbox Alpha 3 overview-documentation specialist. Use for concise product-facing work on the Blackbox overview page and its supporting visuals." + +developer_instructions = """ +You are the persistent Suites Blackbox Alpha 3 overview-documentation agent for this repository. + +Before every task: + +1. Read `/Users/omer/projects/suites/suites.dev/AGENTS.md` completely. +2. Use the configured documentation skills under `/Users/omer/projects/suites/suites.dev/.codex/agents/skills/`. Treat their source guides as a documentation method and reference set, not as automatically accepted product authority. +3. Read the current target page and only the supporting Blackbox files needed for the task. +4. Preserve user changes and unrelated work in the repository. + +Scope: + +- Work only on `src/content/docs/blackbox/overview/what-is-blackbox.mdx` and the Blackbox-specific visuals or components it directly uses, unless the user explicitly expands the scope. +- Write product language, not implementation design. +- Keep the page concise, example-led, scannable, and cognitively light. +- Prefer static SVG or compact terminal-like visuals that clarify a relationship. Do not use Mermaid or screenshots for this page. +- Do not use em dashes on this page. + +Product boundaries: + +- Blackbox provides deterministic behavioral assurance for selected system and E2E flows inside a coding-agent harness. +- Effects are the committed core. +- People approve accepted meaning. Agents may propose and repair implementation, but they cannot redefine success. +- The executable suite realizes accepted behavior. Runtime owns attempt identities, capture lifecycle, and evidence. Analysis produces findings and verdicts. +- Managed capture is fresh for every case attempt, retry, and sequence member. +- Incomplete evidence cannot establish completeness-dependent absence, upper-bound, or exactness satisfaction. +- ODC is experimental, remains separate from ordinary effect findings, and cannot change them. +- Treat research as evidence, not automatically accepted truth. +- Never invent CLI commands, imports, schemas, package APIs, report fields, support claims, or current capabilities. +- Label committed behavior, Alpha targets, experimental capabilities, planned representations, and conceptual previews explicitly. +- Do not use the stale AssurancePlan authority model. + +Current Gherkin decision: + +- The current overview presents `accepted specification -> Gherkin -> system suite -> runtime evidence` as one continuous flow. +- Do not label Gherkin optional on this overview page unless the user reverses that decision. +- Other repository material may still call Gherkin optional. Flag that conflict before generalizing the overview decision into broader product or support documentation. + +Working behavior: + +- Lead with the requested outcome. +- Inspect before editing. +- Make the smallest coherent change. +- Use `apply_patch` for edits. +- Keep status labels and accessibility text explicit; never rely on color alone. +- Validate edited SVG, MDX, or Astro content in proportion to the change. +- Run `pnpm docs:graph` and `pnpm docs:graph:check` after documentation edits. +- Run the Astro build after changing page structure or supporting components. +- Report changed files, validation results, and any unresolved product conflict concisely. +""" + +[[skills.config]] +path = "/Users/omer/projects/suites/suites.dev/.codex/agents/skills/technical-docs-guide/SKILL.md" +enabled = true + +[[skills.config]] +path = "/Users/omer/projects/suites/suites.dev/.codex/agents/skills/divio-docs-introduction/SKILL.md" +enabled = true + +[[skills.config]] +path = "/Users/omer/projects/suites/suites.dev/.codex/agents/skills/divio-docs-system-explanation/SKILL.md" +enabled = true diff --git a/.codex/agents/skills/divio-docs-introduction/SKILL.md b/.codex/agents/skills/divio-docs-introduction/SKILL.md new file mode 100644 index 0000000..02344cb --- /dev/null +++ b/.codex/agents/skills/divio-docs-introduction/SKILL.md @@ -0,0 +1,22 @@ +--- +name: divio-docs-introduction +description: Classify documentation as tutorial, how-to, reference, or explanation before writing. Use when deciding a page's purpose, reader mode, content boundaries, or navigation role. +--- + +# Divio Documentation Classification + +Use this skill to give each documentation page one primary job. + +Read the [Divio introduction](./divio-docs-introduction.md) completely when a +task involves page framing, content classification, or deciding what belongs on +the page. Treat it as documentation-method evidence, not as accepted product +truth. + +For each page: + +- Choose one primary mode: tutorial, how-to, reference, or explanation. +- Match the structure and level of detail to that mode. +- Move or remove content that forces the page to teach, instruct, specify, and + explain at the same time. +- Let the user's requested outcome and repository boundaries override generic + examples from the source. diff --git a/.codex/agents/skills/divio-docs-introduction/divio-docs-introduction.md b/.codex/agents/skills/divio-docs-introduction/divio-docs-introduction.md new file mode 100644 index 0000000..468f5ae --- /dev/null +++ b/.codex/agents/skills/divio-docs-introduction/divio-docs-introduction.md @@ -0,0 +1,98 @@ +[Skip to main content](https://docs.divio.com/documentation-system/introduction/#__docusaurus_skipToContent_fallback) + +On this page + +## The problem and the solution [​](https://docs.divio.com/documentation-system/introduction/\#the-problem-and-the-solution "Direct link to The problem and the solution") + +### The problem it solves [​](https://docs.divio.com/documentation-system/introduction/\#the-problem-it-solves "Direct link to The problem it solves") + +It doesn’t matter how good your product is, because **if its documentation is not good enough, people will not use it**. +Even if they have to use it because they have no choice, without good documentation, they won’t use it effectively or +the way +you’d like them to. + +Nearly everyone understands this. Nearly everyone knows that they need good documentation, and **most people try to +create good documentation**. And **most people fail**. + +Usually, it’s not because they don’t try hard enough. Usually, it’s because they are not doing it the right way. + +This system is a way to make your documentation better, not by working harder at it, but by doing it the right way. * +*The right way is the easier way** \- easier to write, and easier to maintain. + +### The 'secret' [​](https://docs.divio.com/documentation-system/introduction/\#the-secret "Direct link to The 'secret'") + +It's not actually a secret and it certainly shouldn't be: documentation needs to include and be structured around its * +*four different functions**: _tutorials_, _how-to guides_, _technical reference_ and _explanation_. Each of them * +*requires a distinct mode of writing**. People working with software need these four different kinds of documentation at +different times, in different circumstances - so software usually needs them all, and they should all be integrated into +your documentation. + +And documentation needs to be explicitly structured around them, and they all must be kept separate and distinct from +each other. + +| | [Tutorials](https://docs.divio.com/documentation-system/tutorials/) | [How-to guides](https://docs.divio.com/documentation-system/how-to-guides/) | [Reference](https://docs.divio.com/documentation-system/reference/) | [Explanation](https://docs.divio.com/documentation-system/explanation/) | +|---------------|---------------------------------------------------------------------|-----------------------------------------------------------------------------|---------------------------------------------------------------------|-------------------------------------------------------------------------| +| _oriented to_ | learning | a goal | information | understanding | +| _must_ | allow the newcomer to get started | show how to solve a specific problem | describe the machinery | explain | +| _its form_ | a lesson | a series of steps | dry description | discursive explanation | +| _analogy_ | teaching a small child how to cook | a recipe in a cookery book | a reference encyclopedia article | an article on culinary social history | + +This division makes it obvious to both author and reader what material, and what _kind_ of material, goes where. It +tells the +author **how to write**, and **what to write**, and **where to write it**. It saves the author from wasting a great deal +of time +trying to wrestle the information they want to impart into a shape that makes sense, because **each of these kinds of** +**documentation has only one job**. + +In fact, it’s extremely hard to maintain good documentation that doesn’t implicitly or explicitly recognise the +quadrants of this scheme. The demands of each kind are different from those of the others, so **any attempt at +documentation that fails to maintain this structure suffers**, as it’s pulled in different directions at once. + +Once you understand the structure, it becomes a very useful tool for analysing existing documentation, and understanding +what needs to be done to improve it. + +In the following sections, each of these four parts is dealt with in detail. + +## Making documentation work [​](https://docs.divio.com/documentation-system/introduction/\#making-documentation-work "Direct link to Making documentation work") + +### For authors [​](https://docs.divio.com/documentation-system/introduction/\#for-authors "Direct link to For authors") + +One of the biggest headaches that documentation maintainers have to deal with is not having a clear picture of what they +should be doing. They write and rewrite, but find it hard to make it fit together in satisfactory ways. + +This structure resolves those questions by making clear distinctions and separations. They make documentation that is +easier to write and maintain, that’s easier to use and to find one's way around in. + +The documentation doesn’t write itself - but it’s now possible to write it without also having to wrestle with poor fit, +or unclear scope or doubt about what should be included or what style to adopt. It becomes much clearer what to write, +how to write it, and where to put it. + +### For readers [​](https://docs.divio.com/documentation-system/introduction/\#for-readers "Direct link to For readers") + +It serves users better, because for all the different phases in the cycle of their interaction with the software they +will find the right kind of documentation, that serves the needs of that moment. + +Writing documentation that explicitly and distinctly addresses each of the four quadrants helps the software attract and +keep more users, who will use it more effectively - and that is one of the things the creators of software want most of +all. + +Was this helpful? + +[Share Your Thoughts](https://docs.divio.com/) + +- [The problem and the solution](https://docs.divio.com/documentation-system/introduction/#the-problem-and-the-solution) + - [The problem it solves](https://docs.divio.com/documentation-system/introduction/#the-problem-it-solves) + - [The 'secret'](https://docs.divio.com/documentation-system/introduction/#the-secret) +- [Making documentation work](https://docs.divio.com/documentation-system/introduction/#making-documentation-work) + - [For authors](https://docs.divio.com/documentation-system/introduction/#for-authors) + - [For readers](https://docs.divio.com/documentation-system/introduction/#for-readers) + +- [The Documentation System](https://docs.divio.com/documentation-system/) +- [Privacy Policy](https://www.divio.com/terms-and-policies/privacy-notice/customer/) +- [Terms & Policies](https://www.divio.com/terms-and-policies/terms/) + +reCAPTCHA + +Recaptcha requires verification. + +protected by **reCAPTCHA** \ No newline at end of file diff --git a/.codex/agents/skills/divio-docs-system-explanation/SKILL.md b/.codex/agents/skills/divio-docs-system-explanation/SKILL.md new file mode 100644 index 0000000..8ac8cfe --- /dev/null +++ b/.codex/agents/skills/divio-docs-system-explanation/SKILL.md @@ -0,0 +1,24 @@ +--- +name: divio-docs-system-explanation +description: Write concise explanation pages that build understanding through context, relationships, examples, and bounded product claims. Use for overview and conceptual documentation, not procedures or API reference. +--- + +# Divio Explanation Pages + +Use this skill for overview and conceptual pages whose primary job is helping a +reader understand a product or system. + +Read the [Divio explanation guide](./divio-docs-system-explanation.md) +completely before substantial drafting or review. Treat it as writing-method +evidence rather than product authority. + +On an explanation page: + +- Establish why the topic matters and how its parts relate. +- Use one concrete example or compact visual to anchor abstract framing. +- Discuss boundaries and distinctions that prevent a plausible + misunderstanding. +- Avoid turning the page into a sequence of instructions or a machinery + catalog. +- Keep product claims grounded in accepted repository sources and label their + status explicitly. diff --git a/.codex/agents/skills/divio-docs-system-explanation/divio-docs-system-explanation.md b/.codex/agents/skills/divio-docs-system-explanation/divio-docs-system-explanation.md new file mode 100644 index 0000000..146bec4 --- /dev/null +++ b/.codex/agents/skills/divio-docs-system-explanation/divio-docs-system-explanation.md @@ -0,0 +1,83 @@ +[Skip to main content](https://docs.divio.com/documentation-system/explanation/#__docusaurus_skipToContent_fallback) + +On this page + +Explanation, or discussions, _clarify and illuminate a particular topic_. They broaden the documentation’s coverage of a topic. + +They are **understanding-oriented**. + +Explanations can equally well be described as _discussions_; they are discursive in nature. They are a chance for the documentation to relax and step back from the software, taking a wider view, illuminating it from a higher level or even from different perspectives. You might imagine a discussion document being read at leisure, rather than over the code. + +This section of documentation is rarely explicitly created, and instead, snippets of explanation are scattered amongst other sections. Sometimes, the section exists, but has a name such as _Background_ or _Other notes_ or _Key topics_ \- these names are not always useful. + +Discussions are less easy to create than it might seem - things that are straightforward to explain when you have the starting-point of someone’s question are less easy when you have a blank page and have to write down something about it. + +A topic isn’t defined by a specific task you want to achieve, like a how-to guide, or what you want the user to learn, like a tutorial. It’s not defined by a piece of the machinery, like reference material. It’s defined by what **you** think is a reasonable area to try to cover at one time, so the division of topics for discussion can sometimes be a little arbitrary. + +## Analogy from cooking [​](https://docs.divio.com/documentation-system/explanation/\#analogy-from-cooking "Direct link to Analogy from cooking") + +![a child cooking](https://docs.divio.com/assets/images/mcgee-26cf8ab30240e51f1b394afe4de6a716.jpg) + +Think about a work that discusses food and cooking in the context of history, science and technology. It's _about_ cooking and the kitchen. + +It doesn't teach, it's not a collection of recipes, and it doesn't just describe. + +Instead, it analyses, considers things from multiple perspectives. It might explain why it is we now do things the way we do, or +even describe bad ways of doing things, or obscure alternatives. + +It deepens our knowledge and makes it richer, even if it isn't knowledge we can actually apply in any practical sense - but it doesn't need to be, in order to be valuable. + +It's something we might read at our leisure, away from the kitchen itself, when we want +to think about cooking at a higher level, and to understand more about the subject. + +## How to write a good explanation [​](https://docs.divio.com/documentation-system/explanation/\#how-to-write-a-good-explanation "Direct link to How to write a good explanation") + +### Provide context [​](https://docs.divio.com/documentation-system/explanation/\#provide-context "Direct link to Provide context") + +**Explanations are the place for background and context** \- for example, _Web forms and how they are handled in Django_, or _Search in django CMS_. + +They can also explain _why_ things are so - design decisions, historical reasons, technical constraints. + +### Discuss alternatives and opinions [​](https://docs.divio.com/documentation-system/explanation/\#discuss-alternatives-and-opinions "Direct link to Discuss alternatives and opinions") + +**Explanation can consider alternatives**, or multiple different approaches to the same question. For example, in an article on Django deployment, it would be appropriate to consider and evaluate different web server options. + +Discussions can even consider and weigh up contrary _opinions_ \- for example, whether test modules should be in a package directory, or not. + +### Don't instruct, or provide technical reference [​](https://docs.divio.com/documentation-system/explanation/\#dont-instruct-or-provide-technical-reference "Direct link to Don't instruct, or provide technical reference") + +**Explanation should do things that the other parts of the documentation do not.** It’s not the place of an explanation to instruct the user in how to do something. Nor should it provide technical description. These functions of documentation are already taken care of in other sections. + +## Example from Divio's documentation [​](https://docs.divio.com/documentation-system/explanation/\#example-from-divios-documentation "Direct link to Example from Divio's documentation") + +Have a look at [our explanation section](https://docs.divio.com/background/) (titled "Background information" - +the name is not important as long as the purpose is clear). + +![Django explanation example](https://docs.divio.com/assets/images/divio-explanation-example-ebd2c37d25e4038efb9323dfe41d74f1.png) + +These articles don’t teach anything. They don’t tell the user what to do. They aren’t reference guides. They just +discuss particular topics. The user doesn’t _need_ to know about (for example) caching or CDN or how we manage +environment variables in order to use the platform or achieve any particular task, but the time is likely to come when +someone's experience and use of the platform will be improved by having a clearer, better, deeper understanding of +those things. + +These articles provide the bigger picture, the context. Users are human beings; maybe they don’t strictly need to know +why we do a certain thing a certain way, but knowing it might well provide them with a kind of satisfaction and comfort +that makes them a happier user of the product. + +Was this helpful? + +[Share Your Thoughts](https://docs.divio.com/) + +- [Analogy from cooking](https://docs.divio.com/documentation-system/explanation/#analogy-from-cooking) +- [How to write a good explanation](https://docs.divio.com/documentation-system/explanation/#how-to-write-a-good-explanation) + - [Provide context](https://docs.divio.com/documentation-system/explanation/#provide-context) + - [Discuss alternatives and opinions](https://docs.divio.com/documentation-system/explanation/#discuss-alternatives-and-opinions) + - [Don't instruct, or provide technical reference](https://docs.divio.com/documentation-system/explanation/#dont-instruct-or-provide-technical-reference) +- [Example from Divio's documentation](https://docs.divio.com/documentation-system/explanation/#example-from-divios-documentation) + +- [The Documentation System](https://docs.divio.com/documentation-system/) +- [Privacy Policy](https://www.divio.com/terms-and-policies/privacy-notice/customer/) +- [Terms & Policies](https://www.divio.com/terms-and-policies/terms/) + +reCAPTCHA \ No newline at end of file diff --git a/.codex/agents/skills/divio-docs-system-how-to-guides.md b/.codex/agents/skills/divio-docs-system-how-to-guides.md new file mode 100644 index 0000000..fb4074a --- /dev/null +++ b/.codex/agents/skills/divio-docs-system-how-to-guides.md @@ -0,0 +1,98 @@ +[Skip to main content](https://docs.divio.com/documentation-system/how-to-guides/#__docusaurus_skipToContent_fallback) + +On this page + +How-to guides take the reader through the steps required to solve a real-world problem. + +They are recipes, directions to achieve a specific end - for example: _how to create a web form_; _how to plot a three-dimensional data-set_; _how to enable LDAP authentication_. + +They are wholly **goal-oriented**. + +**How-to guides are wholly distinct from tutorials** and must not be confused with them: + +- A tutorial is what you decide a beginner needs to know. +- A how-to guide is an answer to a question that only a user with some experience could even formulate. + +In a how-to guide, you can assume some knowledge and understanding. You can assume that the user already knows how to do basic things and use basic tools. + +Unlike tutorials, how-to guides in software documentation tend to be done fairly well. They’re also fun and easy to write. + +## Analogy from cooking [​](https://docs.divio.com/documentation-system/how-to-guides/\#analogy-from-cooking "Direct link to Analogy from cooking") + +![a recipe](https://docs.divio.com/assets/images/recipe-7494163494495cc2ebd95961fb310cc0.jpg) + +Think about a recipe, for preparing something to eat. + +A recipe has a clear, defined end. It addresses a specific question. It shows someone - who can be assumed to have some basic knowledge already - how to achieve something. + +Someone who has never cooked before can't be expected to follow a recipe with success, so a recipe is not a substitute for a cooking lesson. At the same time, someone who reads a recipe would be irritated to find that it tries to teach basics that they know already, +or contains irrelevant discussion of the ingredients. + +## How to write good how-to guides [​](https://docs.divio.com/documentation-system/how-to-guides/\#how-to-write-good-how-to-guides "Direct link to How to write good how-to guides") + +### Provide a series of steps [​](https://docs.divio.com/documentation-system/how-to-guides/\#provide-a-series-of-steps "Direct link to Provide a series of steps") + +**How-to guides must contain a list of steps, that need to be followed in order** (just like tutorials do). You don’t have to start at the very beginning, just at a reasonable starting point. How-to guides should be reliable, but they don’t need to have the cast-iron repeatability of a tutorial. + +### Focus on results [​](https://docs.divio.com/documentation-system/how-to-guides/\#focus-on-results "Direct link to Focus on results") + +**How-to guides must focus on achieving a practical goal.** Anything else is a distraction. As in tutorials, detailed explanations are out of place here. + +### Solve a particular problem [​](https://docs.divio.com/documentation-system/how-to-guides/\#solve-a-particular-problem "Direct link to Solve a particular problem") + +**A how-to guide must address a specific question or problem**: _How do I …?_ + +This is one way in which how-to guides are distinct from tutorials: when it comes to a how-to guide, the reader can be assumed to know _what_ they should achieve, but don’t yet know _how_ \- whereas in the tutorial, _you_ are responsible for deciding what things the reader needs to know about. + +### Don't explain concepts [​](https://docs.divio.com/documentation-system/how-to-guides/\#dont-explain-concepts "Direct link to Don't explain concepts") + +**A how-to guide should not explain things.** It’s not the place for discussions of that kind; they will simply get in the way of the action. If explanations are important, link to them. + +### Allow for some flexibility [​](https://docs.divio.com/documentation-system/how-to-guides/\#allow-for-some-flexibility "Direct link to Allow for some flexibility") + +**A how-to guide should allow for slightly different ways of doing the same thing.** It needs just enough flexibility in it that the user can see how it will apply to slightly different examples from the one you describe, or understand how to adapt it to a slightly different system or configuration from the one you’re assuming. Don’t be so specific that the guide is useless for anything except the exact purpose you have in mind. + +### Leave things out [​](https://docs.divio.com/documentation-system/how-to-guides/\#leave-things-out "Direct link to Leave things out") + +**Practical usability is more valuable than completeness.** Tutorials need to be complete, end-to-end guides; how-to guides do not. They can start and end where it seems appropriate to you. They don’t need to mention everything that there is to mention either, just because it is related to the topic. A bloated how-to guide doesn’t help the user get speedily to their solution. + +### Name guides well [​](https://docs.divio.com/documentation-system/how-to-guides/\#name-guides-well "Direct link to Name guides well") + +**The title of a how-to document should tell the user exactly what it does.** _How to create a class-based view_ is a good title. _Creating a class-based view_ or worse, _Class-based views_, are not. + +## Example from Divio's documentation [​](https://docs.divio.com/documentation-system/how-to-guides/\#example-from-divios-documentation "Direct link to Example from Divio's documentation") + +Have a look at [our how-to guides](https://docs.divio.com/how-to/). + +![Django how-to example](https://docs.divio.com/assets/images/django-how-to-example-1653a3644b5b1ddf918eaadcb2cc05f6.png) + +Each one of these is an answer to a question, or problem: _how do I...?_ Each title can clearly be preceded by the +words “How to”. Each one is a recipe, that takes you through the steps required to complete a specific task. + +Although both the tutorials and the how-to guides serve the needs of the user, the tutorials are led by the author who +knows what the user needs to know, while the how-to guides are led by the user who asks the questions. + +Was this helpful? + +[Share Your Thoughts](https://docs.divio.com/) + +- [Analogy from cooking](https://docs.divio.com/documentation-system/how-to-guides/#analogy-from-cooking) +- [How to write good how-to guides](https://docs.divio.com/documentation-system/how-to-guides/#how-to-write-good-how-to-guides) + - [Provide a series of steps](https://docs.divio.com/documentation-system/how-to-guides/#provide-a-series-of-steps) + - [Focus on results](https://docs.divio.com/documentation-system/how-to-guides/#focus-on-results) + - [Solve a particular problem](https://docs.divio.com/documentation-system/how-to-guides/#solve-a-particular-problem) + - [Don't explain concepts](https://docs.divio.com/documentation-system/how-to-guides/#dont-explain-concepts) + - [Allow for some flexibility](https://docs.divio.com/documentation-system/how-to-guides/#allow-for-some-flexibility) + - [Leave things out](https://docs.divio.com/documentation-system/how-to-guides/#leave-things-out) + - [Name guides well](https://docs.divio.com/documentation-system/how-to-guides/#name-guides-well) +- [Example from Divio's documentation](https://docs.divio.com/documentation-system/how-to-guides/#example-from-divios-documentation) + +- [The Documentation System](https://docs.divio.com/documentation-system/) +- [Privacy Policy](https://www.divio.com/terms-and-policies/privacy-notice/customer/) +- [Terms & Policies](https://www.divio.com/terms-and-policies/terms/) + +reCAPTCHA + +Recaptcha requires verification. + +protected by **reCAPTCHA** \ No newline at end of file diff --git a/.codex/agents/skills/divio-docs-system-reference.md b/.codex/agents/skills/divio-docs-system-reference.md new file mode 100644 index 0000000..16d0a76 --- /dev/null +++ b/.codex/agents/skills/divio-docs-system-reference.md @@ -0,0 +1,89 @@ +[Skip to main content](https://docs.divio.com/documentation-system/reference/#__docusaurus_skipToContent_fallback) + +On this page + +Reference guides are _technical descriptions of the machinery_ and how to operate it. + +Reference guides have one job only: to describe. They are code-determined, because ultimately that's what they describe: key classes, functions, APIs, and so they should list things like functions, fields, attributes and methods, and set out how to use them. + +Reference material is **information-oriented**. + +By all means technical reference can contain examples to illustrate usage, but it should not attempt to explain basic concepts, or how to achieve common tasks. + +Reference material should be **simple and to the point**. + +Note that description **does** include basic description of how to use the machinery - how to instantiate a particular class, or invoke a certain method, for example, or precautions that must be taken when passing something to a function. However this is simply part of its function as technical reference, and emphatically **not** to be confused with a how-to guide - _describing correct usage of software_ (technical reference) is not the same as _showing how to use it to achieve a certain end_ (how-to documentation). + +For some developers, reference guides are the only kind of documentation they can imagine. They already understand their software, they know how to use it. All they can imagine that other people might need is technical information about it. + +Reference material tends to be written well. It can even - to some extent - be generated automatically, but this is never sufficient on its own. + +## Analogy from cooking [​](https://docs.divio.com/documentation-system/reference/\#analogy-from-cooking "Direct link to Analogy from cooking") + +![a child cooking](https://docs.divio.com/assets/images/ginger-91895be3364d9305957fd85da57e7817.jpg) + +Consider an encyclopedia article about an ingredient, say ginger. + +When you look up _ginger_ in a reference work, what you want is _information_ about the ingredient - information describing its provenance, its behaviour, its chemical constituents, how it can be cooked. + +You expect that whatever ingredient you look up, the information will be presented in a similar way. And you expect to be informed of +basic facts, such as _ginger is a member of the family that includes turmeric and cardamom_. + +This is also where you'd expect to be alerted about potential problems, such as: _ginger is known to provoke heartburn in some_ +_individuals_ or: _ginger may interfere with the effects of anticoagulants, such as warfarin or aspirin_. + +## How to write good reference guides [​](https://docs.divio.com/documentation-system/reference/\#how-to-write-good-reference-guides "Direct link to How to write good reference guides") + +### Structure the documentation around the code [​](https://docs.divio.com/documentation-system/reference/\#structure-the-documentation-around-the-code "Direct link to Structure the documentation around the code") + +**Give reference documentation the same structure as the codebase**, so that the user can navigate both the code and the documentation for it at the same time. This will also help the maintainers see where reference documentation is missing or needs to be updated. + +### Be consistent [​](https://docs.divio.com/documentation-system/reference/\#be-consistent "Direct link to Be consistent") + +**In reference guides, structure, tone, format must all be consistent** \- as consistent as those of an encyclopaedia or dictionary. + +### Do nothing but describe [​](https://docs.divio.com/documentation-system/reference/\#do-nothing-but-describe "Direct link to Do nothing but describe") + +**The only job of technical reference is to describe**, as clearly and completely as possible. Anything else (explanation, discussion, instruction, speculation, opinion) is not only a distraction, but will make it harder to use and maintain. Provide examples to illustrate the description when appropriate. + +Avoid the temptation to use reference material to instruct in how to achieve things, beyond the basic scope of using the software, and don’t allow explanations of concepts or discussions of topics to develop. Instead, link to how-to guides, explanation and introductory tutorials as appropriate. + +### Be accurate [​](https://docs.divio.com/documentation-system/reference/\#be-accurate "Direct link to Be accurate") + +**These descriptions must be accurate and kept up-to-date.** Any discrepancy between the machinery and your description of it will inevitably lead a user astray. + +## Example from Divio's documentation [​](https://docs.divio.com/documentation-system/reference/\#example-from-divios-documentation "Direct link to Example from Divio's documentation") + +Have a look at [an example from our technical reference section](https://docs.divio.com/divio-cli/reference/). + +![Django reference example](https://docs.divio.com/assets/images/django-reference-example-6991894d7ea6aea260373b81d74bc892.png) + +This is a typical reference guide (in this case, for our Divio CLI). + +Description is all this article does, setting out in a complete and accurate form the functions, commands and options +of the tool. + +It's hardly a friendly or engaging read, but its purpose is to make looking up information about functionality as swift +and distraction-free as possible. + +Was this helpful? + +[Share Your Thoughts](https://docs.divio.com/) + +- [Analogy from cooking](https://docs.divio.com/documentation-system/reference/#analogy-from-cooking) +- [How to write good reference guides](https://docs.divio.com/documentation-system/reference/#how-to-write-good-reference-guides) + - [Structure the documentation around the code](https://docs.divio.com/documentation-system/reference/#structure-the-documentation-around-the-code) + - [Be consistent](https://docs.divio.com/documentation-system/reference/#be-consistent) + - [Do nothing but describe](https://docs.divio.com/documentation-system/reference/#do-nothing-but-describe) + - [Be accurate](https://docs.divio.com/documentation-system/reference/#be-accurate) +- [Example from Divio's documentation](https://docs.divio.com/documentation-system/reference/#example-from-divios-documentation) + +- [The Documentation System](https://docs.divio.com/documentation-system/) +- [Privacy Policy](https://www.divio.com/terms-and-policies/privacy-notice/customer/) +- [Terms & Policies](https://www.divio.com/terms-and-policies/terms/) + +reCAPTCHA + +Recaptcha requires verification. + +protected by **reCAPTCHA** \ No newline at end of file diff --git a/.codex/agents/skills/divio-docs-system-structure.md b/.codex/agents/skills/divio-docs-system-structure.md new file mode 100644 index 0000000..73c9184 --- /dev/null +++ b/.codex/agents/skills/divio-docs-system-structure.md @@ -0,0 +1,67 @@ +[Skip to main content](https://docs.divio.com/documentation-system/structure/#__docusaurus_skipToContent_fallback) + +On this page + +## Why isn't this obvious? [​](https://docs.divio.com/documentation-system/structure/\#why-isnt-this-obvious "Direct link to Why isn't this obvious?") + +This structure is clear, and it works, but there is a reason why it's not so obvious, and that is the way the characteristics of each quadrant of the documentation overlap with those of its neighbours in the scheme. + +![overview of the documentation system](https://docs.divio.com/assets/images/overview-8b21327c9a55ca08c6712f26bda2113c.png) + +Each of the quadrants is similar to its two neighbours: + +- _tutorials and how-to guides_ are both concerned with **describing practical steps** +- _how-to guides and technical reference_ are both **what we need when we are at work, coding** +- _reference guides and explanation_ are both concerned with **theoretical knowledge** +- _tutorials and explanation_ are both **most useful when we are studying**, rather than actually working + +### The tendency to collapse [​](https://docs.divio.com/documentation-system/structure/\#the-tendency-to-collapse "Direct link to The tendency to collapse") + +Given these overlaps, it's not surprising that the different kinds of documentation become confused and mixed in with each other. In fact, there is a natural gravitational pull of these distinct types of documentation to each other, and it is hard to resist. Its effect is to collapse the structure, and that is why so much documentation looks like this: + +![collapse of the documentation structure](https://docs.divio.com/assets/images/collapse-a5ace8c92dc0447171d593c0f2848fc0.png) + +## Adoption of the system [​](https://docs.divio.com/documentation-system/structure/\#adoption-of-the-system "Direct link to Adoption of the system") + +Though it's rare to find clear examples of it used fully, a great deal of documentation recognises, in different ways, each of these four functions. + +Good examples of the scheme in substantial projects include: + +- the [Divio Developer Handbook](https://docs.divio.com/) +- [Django's documentation](https://docs.djangoproject.com/en/4.2/#how-the-documentation-is-organized) +- [django CMS's documentation](http://docs.django-cms.org/) + +Sometimes the documentation is so minimal that not all quadrants are ready to be represented, as in the case of [Getting started with Java and Spring-boot](https://github.com/flavours/getting-started-with-spring-boot/blob/master/README.md), which includes +only a tutorial, how-to and reference material. + +In each case though, however minimal or even incomplete, the system is respected and the clear distinction between sections and +their purposes will benefit the author and user right away, and help guide the expansion of the material as it develops in the +future. + +## About the analysis and its application [​](https://docs.divio.com/documentation-system/structure/\#about-the-analysis-and-its-application "Direct link to About the analysis and its application") + +The analysis of documentation in this article is based on several years of experience writing and maintaining documentation, and much time spent considering how to improve it. + +It’s also based on sound principles that come from a variety of disciplines. For example, its conception of tutorials has a pedagogical basis; it posits a tutor and a learner, and considers using software to be a craft in which abstract understanding of general principles follows from concrete steps that deal with particulars. + +The system is presented regularly at talks and interactive workshops. The analysis has been applied to numerous projects, including large internal documentation sets, and has repeatedly procured +benefits of usability and maintainability, across a very wide range of technical subject matter. + +Was this helpful? + +[Share Your Thoughts](https://docs.divio.com/) + +- [Why isn't this obvious?](https://docs.divio.com/documentation-system/structure/#why-isnt-this-obvious) + - [The tendency to collapse](https://docs.divio.com/documentation-system/structure/#the-tendency-to-collapse) +- [Adoption of the system](https://docs.divio.com/documentation-system/structure/#adoption-of-the-system) +- [About the analysis and its application](https://docs.divio.com/documentation-system/structure/#about-the-analysis-and-its-application) + +- [The Documentation System](https://docs.divio.com/documentation-system/) +- [Privacy Policy](https://www.divio.com/terms-and-policies/privacy-notice/customer/) +- [Terms & Policies](https://www.divio.com/terms-and-policies/terms/) + +reCAPTCHA + +Recaptcha requires verification. + +protected by **reCAPTCHA** \ No newline at end of file diff --git a/.codex/agents/skills/docs-divio-com-documentation-system-tutorials.md b/.codex/agents/skills/docs-divio-com-documentation-system-tutorials.md new file mode 100644 index 0000000..b2034b6 --- /dev/null +++ b/.codex/agents/skills/docs-divio-com-documentation-system-tutorials.md @@ -0,0 +1,124 @@ +Tutorials are _lessons_ that take the reader by the hand through a series of steps to complete a project of some kind. They are what your project needs in order to show a beginner that they can achieve something with it. + +They are wholly **learning-oriented**, and specifically, they are oriented towards _learning how_ rather than _learning what_. + +**You are the teacher**, and you are **responsible** for what the student will do. Under **your** instruction, the student will execute a series of actions to achieve some **end**. + +The end and the actions are up to you, but deciding what they should be can be hard work. The end has to be _meaningful_, but also _achievable_ for a complete beginner. + +The important thing is that having done the tutorial, the learner is in a position to make sense of the rest of the documentation, and the software itself. + +Most software projects have really bad - or non-existent - tutorials. Tutorials are what will turn your learners into users. **A bad or missing tutorial will prevent your project from acquiring new users.** + +Of the sections describing the four kinds of documentation, this is by far the longest - that's because tutorials are the most +misunderstood and most difficult to do well. The best way of teaching is to have a teacher present, interacting with the +student. That's rarely possible, and our written tutorials will be at best a far-from-perfect substitute. That's all the more +reason to pay special attention to them. + +Tutorials need to be useful for the beginner, easy to follow, meaningful and extremely robust, and kept up-to-date. You might +well find that writing and maintaining your tutorials can occupy as much time and energy as the other three parts put +together. + +## Analogy from cooking [​](https://docs.divio.com/documentation-system/tutorials/\#analogy-from-cooking "Direct link to Analogy from cooking") + +![a child cooking](https://docs.divio.com/assets/images/anselmo-9b3f2c9a5b227dc809132c81648652ad.jpg) + +Consider an analogy of teaching a child to cook. + +_What_ you teach the child to cook isn’t really important. What’s important is that the child finds it enjoyable, and gains confidence, and wants to do it again. + +_Through_ the activities the child engages in, they will learn important aspects of cooking. They will gain experience with being in the kitchen, using the utensils, and handling the food. + +This is because using **software, like cooking, is a matter of craft**. It’s knowledge - but it is _practical_ knowledge, not _theoretical_ knowledge. + +When we learn a new craft or skill, we always begin learning it by doing. + +## How to write good tutorials [​](https://docs.divio.com/documentation-system/tutorials/\#how-to-write-good-tutorials "Direct link to How to write good tutorials") + +### Allow the user to learn by doing [​](https://docs.divio.com/documentation-system/tutorials/\#allow-the-user-to-learn-by-doing "Direct link to Allow the user to learn by doing") + +**In the beginning, we only learn anything by doing** \- it’s how we learn to talk, or walk. + +In your software tutorial, your learner needs to _do_ things. The different things that they do while following your tutorial need to cover a wide range of tools and operations, building up from the simplest ones at the start to more complex ones. + +### Get the user started [​](https://docs.divio.com/documentation-system/tutorials/\#get-the-user-started "Direct link to Get the user started") + +It’s perfectly acceptable if your beginner’s first steps are hand-held baby steps. It’s also perfectly acceptable if what you get the beginner to do is not the way an experienced person would, or even if it’s not the ‘correct’ way - a tutorial for beginners is not the same thing as a manual for best practice. + +The point of a tutorial is to get your learner **started on their journey**, not to get them to a final destination. + +### Make sure that your tutorial works [​](https://docs.divio.com/documentation-system/tutorials/\#make-sure-that-your-tutorial-works "Direct link to Make sure that your tutorial works") + +One of your jobs as a tutor is to inspire the beginner’s confidence: in the software, in the tutorial, in the tutor and, of course, in their own ability to achieve what’s being asked of them. + +There are many things that contribute to this. A friendly tone helps, as does consistent use of language, and a logical progression through the material. But the single most important thing is that **what you ask the beginner to do must work**. The learner needs to see that the actions you ask them to take have the effect you say they will have. + +If the learner's actions produce an error or unexpected results, your tutorial has failed - even if it’s not your fault. When your students are there with you, you can rescue them; if they’re reading your documentation on their own you can’t - so you have to prevent that from happening in advance. This is without doubt easier said than done. + +### Ensure the user sees results immediately [​](https://docs.divio.com/documentation-system/tutorials/\#ensure-the-user-sees-results-immediately "Direct link to Ensure the user sees results immediately") + +**Everything the learner does should accomplish something comprehensible, however small.** If your student has to do strange and incomprehensible things for two pages before they even see a result, that’s much too long. The effect of every action should be visible and evident as soon as possible, and the connection to the action should be clear. + +The conclusion of each section of a tutorial, or the tutorial as a whole, must be a meaningful accomplishment. + +### Make your tutorial repeatable [​](https://docs.divio.com/documentation-system/tutorials/\#make-your-tutorial-repeatable "Direct link to Make your tutorial repeatable") + +**Your tutorial must be reliably repeatable.** This not easy to achieve: people will be coming to it with different operating systems, levels of experience and tools. What’s more, any software or resources they use are quite likely themselves to change in the meantime. + +The tutorial has to work for all of them, every time. + +Tutorials unfortunately need regular and detailed testing to make sure that they still work. + +### Focus on concrete steps, not abstract concepts [​](https://docs.divio.com/documentation-system/tutorials/\#focus-on-concrete-steps-not-abstract-concepts "Direct link to Focus on concrete steps, not abstract concepts") + +**Tutorials need to be concrete**, built around specific, particular actions and outcomes. + +The temptation to introduce abstraction is huge; it is after all how most computing derives its power. But all learning proceeds from the particular and concrete to the general and abstract, and asking the learner to appreciate levels of abstraction before they have even had a chance to grasp the concrete is poor teaching. + +### Provide the minimum necessary explanation [​](https://docs.divio.com/documentation-system/tutorials/\#provide-the-minimum-necessary-explanation "Direct link to Provide the minimum necessary explanation") + +**Don’t explain anything the learner doesn’t need to know in order to complete the tutorial.** Extended discussion is important - just not in a tutorial. In a tutorial, it is an obstruction and a distraction. Only the bare minimum is appropriate. Instead, link to explanations elsewhere in the documentation. + +### Focus only on the steps the user needs to take [​](https://docs.divio.com/documentation-system/tutorials/\#focus-only-on-the-steps-the-user-needs-to-take "Direct link to Focus only on the steps the user needs to take") + +**Your tutorial needs to be focused on the task in hand.** Maybe the command you’re introducing has many other options, or maybe there are different ways to access a certain API. It doesn’t matter: right now, your learner does not need to know about those in order to make progress. + +## Example from Divio's documentation [​](https://docs.divio.com/documentation-system/tutorials/\#example-from-divios-documentation "Direct link to Example from Divio's documentation") + +Have a look at [our tutorials](https://docs.divio.com/introduction/). + +![Django tutorial example](https://docs.divio.com/assets/images/django-tutorial-example-f041ad55405f583149da77281d500bc3.png) + +In particular, see the tutorial for Django. The promise that the tutorial makes is: if you have the basic knowledge +required to follow this tutorial, and you follow its directions, you will end up with with a working Django web +application, complete with Postgres database, S3 media storage, and so on. In order to work as a tutorial, it has to +fulfil that promise. + +Note that it doesn’t tell you what you will _learn_, just what you will _do_. The learning comes out of that doing. The +tutorial takes full responsibility for what you will do and the order in which you will do it. + +Was this helpful? + +[Share Your Thoughts](https://docs.divio.com/) + +- [Analogy from cooking](https://docs.divio.com/documentation-system/tutorials/#analogy-from-cooking) +- [How to write good tutorials](https://docs.divio.com/documentation-system/tutorials/#how-to-write-good-tutorials) + - [Allow the user to learn by doing](https://docs.divio.com/documentation-system/tutorials/#allow-the-user-to-learn-by-doing) + - [Get the user started](https://docs.divio.com/documentation-system/tutorials/#get-the-user-started) + - [Make sure that your tutorial works](https://docs.divio.com/documentation-system/tutorials/#make-sure-that-your-tutorial-works) + - [Ensure the user sees results immediately](https://docs.divio.com/documentation-system/tutorials/#ensure-the-user-sees-results-immediately) + - [Make your tutorial repeatable](https://docs.divio.com/documentation-system/tutorials/#make-your-tutorial-repeatable) + - [Focus on concrete steps, not abstract concepts](https://docs.divio.com/documentation-system/tutorials/#focus-on-concrete-steps-not-abstract-concepts) + - [Provide the minimum necessary explanation](https://docs.divio.com/documentation-system/tutorials/#provide-the-minimum-necessary-explanation) + - [Focus only on the steps the user needs to take](https://docs.divio.com/documentation-system/tutorials/#focus-only-on-the-steps-the-user-needs-to-take) +- [Example from Divio's documentation](https://docs.divio.com/documentation-system/tutorials/#example-from-divios-documentation) + +- [The Documentation System](https://docs.divio.com/documentation-system/) +- [Privacy Policy](https://www.divio.com/terms-and-policies/privacy-notice/customer/) +- [Terms & Policies](https://www.divio.com/terms-and-policies/terms/) + +reCAPTCHA + +Recaptcha requires verification. + +protected by **reCAPTCHA** \ No newline at end of file diff --git a/.codex/agents/skills/technical-docs-guide/SKILL.md b/.codex/agents/skills/technical-docs-guide/SKILL.md new file mode 100644 index 0000000..81bd042 --- /dev/null +++ b/.codex/agents/skills/technical-docs-guide/SKILL.md @@ -0,0 +1,23 @@ +--- +name: technical-docs-guide +description: Apply audience-aware, concise, maintainable developer-documentation practices when writing or reviewing product and technical docs. Use for documentation structure, clarity, lifecycle, and source-of-truth decisions. +--- + +# Technical Documentation Guide + +Use this skill to keep developer documentation useful during real work rather +than treating it as a one-time publication artifact. + +Before substantial writing or structural review, read the +[source guide](./technical-docs-guide.md) completely. Treat it as methodology +and supporting evidence, not as product authority. + +Apply these decisions: + +- Identify the reader, their task, and the page type before choosing structure. +- Keep explanation, tutorial, how-to, and reference responsibilities distinct. +- Prefer concise examples and maintained evidence over unsupported abstraction. +- Separate generated or mechanically verified facts from prose that requires + product judgment. +- Preserve explicit user direction and repository product boundaries when they + differ from generic guidance in the source. diff --git a/.codex/agents/skills/technical-docs-guide/technical-docs-guide.md b/.codex/agents/skills/technical-docs-guide/technical-docs-guide.md new file mode 100644 index 0000000..333bd51 --- /dev/null +++ b/.codex/agents/skills/technical-docs-guide/technical-docs-guide.md @@ -0,0 +1,311 @@ +[Back to blog](https://buildwithfern.com/blog) [View as Markdown](https://buildwithfern.com/post/how-to-write-technical-documentation.md) + +# How to write technical documentation: a complete guide (August 2026) + +August 7, 2026·12 min read + +[Educational](https://buildwithfern.com/post/tag/educational), [Docs](https://buildwithfern.com/post/tag/docs) + +Technical documentation fails for a predictable reason: it is written once, by hand, against a system that keeps +changing. The page that explained authentication in March describes an endpoint that was renamed in May, and nobody +notices until a developer opens a support ticket. Writing quality matters, but it is not the constraint. The constraint +is whether the docs have a source of truth that updates without a human remembering to update it. This guide walks the +full process, from audience mapping through publishing, on one +principle: [every piece of documentation should be classified by whether it can be generated or must be written](https://buildwithfern.com/), +and the generated parts should never be maintained by hand. + +**TLDR:** + +- Technical documentation splits into four content types (tutorials, how-to guides, reference, explanation), and mixing + them on one page is the most common structural mistake. +- Reference content should be generated from an API definition. Conceptual content should be written by a human. + Treating both the same way is what produces drift. +- A technical documentation template enforces consistency far more reliably than a style guide nobody reads. +- Style, link, and spec checks belong in CI, where they block a pull request, not in a review checklist. +- Documentation now has two audiences: developers reading in a browser and AI agents parsing structured formats like + `llms.txt` and Markdown endpoints. +- Fern generates API reference docs, multi-language code snippets, and AI-readable formats from a single API definition, + so the generated half of the docs stays current without manual work. + +## The four types of technical documentation + +Most documentation problems are classification problems. A page that tries to teach, instruct, and specify at the same +time does all three badly. The [Diátaxis framework](https://diataxis.fr/) is the clearest articulation of this, and it +splits documentation into four types based on what the reader needs: + +- **Tutorials** are lessons. The reader is learning, and the goal is a successful first experience, not comprehensive + coverage. A tutorial has one path and no branches. +- **How-to guides** are recipes. The reader already knows the system and has a specific goal ("rotate an API key", " + handle webhook retries"). They assume context that a tutorial cannot. +- **Reference** is specification. Endpoint paths, parameters, response schemas, error codes, configuration options. It + is consulted, not read, and its only job is accuracy and completeness. +- **Explanation** is context. Why the system works this way, what tradeoffs were made, how the pieces relate. It is the + type most often skipped and the type that reduces the most support load. + +The practical value of this split is not tidiness. It tells you what can be automated. Reference is machine-derivable +from an API definition. The other three are not. Once a team internalizes that distinction, the maintenance question +answers itself: reference should never be hand-written, and tutorials should never be auto-generated. + +## Step 1: Map the audience before writing a word + +"Developers" is not an audience. A first-time evaluator deciding whether an API can do the job, an implementing engineer +wiring up a webhook handler, and a compliance reviewer confirming how data is encrypted all read the same docs site with +incompatible needs. + +Write down the two or three concrete roles the documentation serves, then the single question each one arrives with. +Evaluators want to know whether a capability exists. Implementers want a working request in their language. Reviewers +want the security model stated plainly. If a page does not answer one of those questions, it belongs somewhere else or +nowhere. + +This matters structurally, not just editorially. Documentation that serves multiple audiences from one source needs a +mechanism for filtering, whether that is audience tagging in the API definition, role-based access control on sections, +or simply separate top-level navigation. Building the filter later means restructuring content that has already +accumulated inbound links. + +## Step 2: Pick the source of truth before picking the tool + +The most consequential decision in a documentation project is where the truth lives. Three patterns are common: + +| Pattern | Source of truth | Drift risk | Best for | +|------------------|----------------------------------------------|----------------------------------|--------------------------------------------| +| WYSIWYG platform | The platform's database | High for reference content | Small surface areas, non-technical authors | +| Docs-as-code | Markdown/MDX in Git | Medium, bounded by review | Conceptual content, engineering-owned docs | +| Spec-driven | API definition (OpenAPI, AsyncAPI, protobuf) | Low for anything derived from it | API reference, code samples, SDKs | + +Production documentation usually needs the last two together. Conceptual content lives in Markdown alongside the code, +reviewed through pull requests like any other change, which is the core +of [docs-as-code](https://buildwithfern.com/post/docs-as-code). Reference content is derived from +the [API definition](https://buildwithfern.com/learn/api-definitions/overview/what-is-an-api-definition) so it cannot +describe an endpoint that no longer exists. + +The failure mode to avoid is a platform where reference content is typed into a web form. That content has no upstream, +so nothing can validate it, and it degrades quietly from the day it is published. + +## Step 3: Build a technical documentation template + +A template does what a style guide cannot: it makes the correct structure the path of least resistance. Every page type +gets a skeleton, and authors fill it in rather than deciding structure from scratch. + +A workable how-to guide template looks like this: + +``` +--- +title: +description: +--- + +## Prerequisites +- Required credentials, permissions, or prior setup +- Minimum SDK or API version + +## Steps +1. — with a runnable code sample +2. — with the expected output or response +3. + +## Verify it worked + + +## Troubleshooting +| Symptom | Likely cause | Fix | + +## Related +- Links to the reference pages for every endpoint used above +``` + +Three details carry most of the value. Prerequisites stated up front prevent the reader from discovering a missing +permission at step six. A verification step is what separates following the instructions from confirming the result. And +a troubleshooting table captures the support questions this page will otherwise generate. + +Reference pages need a template too, though it is usually enforced by the generator rather than the author: endpoint +summary, auth requirements, parameters with types and constraints, request example, response schema, and every +documented error code. Tutorials need a stated end state and an explicit scope boundary so they do not sprawl into +how-to guides. + +Store the templates in the repository and reference them from a contributing guide. If the documentation platform +supports [reusable snippets](https://buildwithfern.com/learn/docs/writing-content/reusable-snippets), put shared +blocks (auth setup, installation, rate-limit notes) in one place so a change propagates everywhere instead of leaving +eleven copies to find. + +## Step 4: Generate the reference, write the concepts + +This is where the guide's thesis becomes operational. Reference documentation is a projection of the API contract. If it +is written by hand, it is a second copy of the contract that has to be kept in sync with the first, and that +synchronization is done by human memory under deadline pressure. + +Generating reference from the specification removes the second copy. Parameter types, required fields, enum values, +error schemas, and code samples in every supported language all derive from the same definition that produces the server +contract and the client libraries. When an endpoint gains a field, the docs gain it in the same commit. + +What generation cannot produce is judgment. An OpenAPI spec can say that `retry_policy` accepts an integer between 0 and +5. It cannot say that setting it above 3 will trip most gateway timeouts, or that idempotency keys are required if the +value is greater than 0. That belongs in hand-written explanation, and it is where documentation earns its keep. +The [API documentation best practices](https://buildwithfern.com/post/api-documentation-best-practices-guide) that +consistently correlate with fast integrations are almost all in this category: worked end-to-end examples, error +recovery guidance, and explicit statements of what the API will not do. + +## Step 5: Write procedures a developer can finish in one sitting + +Prose quality in technical documentation reduces to a short list of habits. + +- **Lead with the task, not the tool.** "Verify webhook signatures" beats "About the signature verification module". +- **Use imperative mood in steps.** "Send a POST request to /v1/tokens" is unambiguous. "The token can be requested" is + not. +- **Make every code sample runnable.** Placeholder-heavy snippets that fail on paste are worse than no snippet, because + the reader spends time debugging the example instead of the integration. +- **State the response, not just the request.** A step is incomplete until the reader knows what success looks like. +- **Put constraints where they apply.** Rate limits belong on the endpoint that enforces them, not only on a page + called "Rate limits". +- **Define a term once, then use it consistently.** Alternating between "workspace", "org", and "tenant" for the same + object forces the reader to hold a mapping in their head. + +Adopting an existing style guide (the [Google developer documentation style guide](https://developers.google.com/style) +or the Microsoft Writing Style Guide) is faster than writing one, and both are specific enough to be +machine-enforceable, which matters for the next step. + +## Step 6: Enforce quality in CI, not in review + +Documentation review that depends on a human noticing a broken link or an inconsistent term does not scale past a +handful of contributors. The checks that actually hold are the ones that fail a build. + +Four gates cover most of it: + +- **Prose linting.** [Vale](https://vale.sh/) checks Markdown and MDX against a style guide as a set of rules, including + terminology bans, passive-voice limits, and heading capitalization. It runs as a GitHub Action that annotates pull + requests inline, and Fern documents a `.vale.ini` setup that parses MDX as Markdown so the rules apply to + component-heavy pages. +- **Spec linting.** [Spectral](https://stoplight.io/open-source/spectral) validates the OpenAPI document itself: missing + descriptions, undocumented error responses, naming conventions, unused schemas. Because reference docs are generated + from the spec, a spec lint is a documentation lint. +- **Breaking-change detection.** Diffing the current API definition against the last released version in CI catches a + removed field before it reaches a published page and a shipped SDK. +- **Link and snippet checking.** Dead internal links and code samples that no longer compile are the two defects readers + report most often, and both are trivially automatable. + +Running these on every pull request converts documentation quality from a periodic cleanup project into a property of +the pipeline. The broader case for this is covered +in [documentation linting](https://buildwithfern.com/post/docs-linting-guide), and the structural side, how content is +organized so it stays findable as it grows, +in [information architecture for documentation](https://buildwithfern.com/post/information-architecture-best-practices-documentation). + +## Step 7: Publish for developers and AI agents + +Documentation now has a second reader that never asks a clarifying question. Coding assistants like Cursor, Claude Code, +and GitHub Copilot pull documentation into context and generate integration code from it. When they read an inaccurate +page, they emit code that fails in a way the developer attributes to the API, not to the docs. + +Serving that audience is mostly a publishing concern rather than a writing one: + +- **`llms.txt`** gives AI clients a token-efficient index of the documentation. + Fern [generates and maintains it automatically](https://buildwithfern.com/learn/docs/ai-features/llms-txt). +- **Markdown endpoints.** Serving the raw Markdown of any page lets agents fetch clean content instead of scraping + rendered HTML. On Fern sites, appending `.md` to a page URL returns its Markdown source, which pairs with `llms.txt` + as an index rather than requiring a full-site concatenation. +- **An MCP server** exposes the documentation as a structured, queryable source for AI clients. Fern generates one from + the documentation site and enforces the same authentication and role-based access control the site applies, so an + agent only reaches content the authenticated user can see. +- **Machine-readable discovery.** Fern documentation sites expose a `/.well-known/api-catalog` endpoint conforming + to [RFC 9727](https://www.rfc-editor.org/rfc/rfc9727.html), so crawlers and agents can enumerate available APIs + without configuration. + +Writing choices help here too, and they overlap with writing well for humans: descriptive headings, one concept per +section, explicit parameter descriptions, and complete error documentation. The specifics are covered +in [writing LLM-friendly documentation](https://buildwithfern.com/post/how-to-write-llm-friendly-documentation). + +## Technical documentation examples worth studying + +Reading good documentation is a faster way to calibrate than reading advice about documentation. + +- **[Stripe](https://docs.stripe.com/)** is the reference standard for API docs: a two-pane layout pairing prose with + runnable samples, language switching that persists across pages, and reference content generated from the spec. +- **[Twilio](https://www.twilio.com/docs)** handles a large multi-product surface without losing navigability, and its + per-endpoint error tables are unusually complete. +- **[Kubernetes](https://kubernetes.io/docs/home/)** separates tutorials, tasks, concepts, and reference at the top + level of navigation, which is the Diátaxis split applied at a very large content scale. +- **[Django](https://docs.djangoproject.com/)** demonstrates that explanation content ages well when tutorials and + reference do not; its topic guides remain useful across major versions. +- **[Postman's API docs](https://learning.postman.com/docs/introduction/overview/)** show how to document a tool whose + primary users are themselves building APIs, with heavy use of task-oriented how-to structure. + +The common thread is not visual polish. It is that each one makes the content type of a page obvious within the first +two lines. + +## How Fern fits into a technical documentation workflow + +Fern takes an API definition (OpenAPI, AsyncAPI, OpenRPC, gRPC/protobuf, or Fern Definition) and generates the +interactive API reference, code snippets in every supported SDK language, and AI-readable formats including `llms.txt` +and a documentation MCP server. Conceptual pages stay as MDX in Git and go through pull-request review, with role-based +access control and `x-fern-audiences` filtering so one source can serve public, partner, and internal readers without +duplicated sites. For contributors who do not work in Git, the Fern Editor provides a WYSIWYG interface that still +commits through the same review workflow. The result is a clean split: the generated half of the docs cannot drift from +the API, and the written half gets the review attention it actually needs. + +## Final thoughts on writing technical documentation + +The question that determines whether documentation stays accurate is not how well it is written. It is whether each page +is classified correctly as generated or authored, and then handled accordingly. Reference derived from a specification +stays true by construction. Tutorials, guides, and explanation stay true through templates, CI checks, and review +discipline. Teams that blur the two end up hand-maintaining machine-derivable content and under-investing in the content +only a human can write. + +[Book a demo](https://buildwithfern.com/book-demo) to see how Fern generates API reference documentation, SDK code +samples, and AI-readable formats from a single definition, so the reference half of your docs maintains itself. + +## FAQ + +### What should a technical documentation template include? + +At minimum: a title stating the task or object, a one-sentence description of the outcome, prerequisites, numbered steps +with runnable code samples, a verification step confirming success, a troubleshooting table, and links to related +reference pages. Keep separate templates for tutorials, how-to guides, and reference so authors are not forced to adapt +one shape to three jobs. Store the templates in the documentation repository so they are versioned alongside the +content. + +### What are the four types of technical documentation? + +Tutorials (learning-oriented lessons), how-to guides (goal-oriented recipes), reference (information-oriented +specification), and explanation (understanding-oriented context). The distinction comes from the Diátaxis framework, and +its practical value is that reference is machine-generable from an API definition while the other three require a human +author. Separating them in navigation also helps readers land on the right page from search. + +### How is technical documentation different from API documentation? + +API documentation is a subset of technical documentation focused on the interface a program exposes: endpoints, +parameters, schemas, authentication, and error codes. Technical documentation covers a wider set of artifacts including +architecture overviews, runbooks, release notes, internal design docs, and end-user guides. The two share tooling and +process, but API reference is uniquely well suited to generation because a machine-readable contract already exists. + +### What tools are used to write technical documentation? + +Docs-as-code stacks typically combine Markdown or MDX in Git, a static or platform-based renderer, Vale for prose +linting, Spectral for OpenAPI linting, and a CI pipeline that runs all of it on pull requests. Diagramming usually +happens in Mermaid or a design tool, and screenshots are best avoided in favor of code samples that can be tested. For +API-heavy documentation, the generator matters more than the editor, because it determines whether reference content can +drift. + +### How do you keep technical documentation from becoming outdated? + +Generate everything that can be generated, and put automated checks on the rest. Reference content derived from the API +definition updates with the spec. For hand-written content, schedule review by ownership rather than by calendar: assign +each section an owner, trigger review when the related code changes, and use CI to catch broken links, style violations, +and spec +drift. [Ongoing documentation maintenance](https://buildwithfern.com/post/documentation-maintenance-best-practices) is a +process problem more than a writing problem. + +August 7, 2026 + +DEV + +TrailLineFern + +Products + +[DocsBeautiful, customizable documentation websites.](https://buildwithfern.com/docs) [SDKsClient libraries designed by language experts.](https://buildwithfern.com/sdks) [CLIInteract with your API from the terminal.](https://buildwithfern.com/cli) + +Resources + +[DocumentationLearn how to use Fern](https://buildwithfern.com/learn) [CareersWe're hiring](https://buildwithfern.com/careers) [Trust centerSOC 2 compliant and secure](https://buildwithfern.com/trust) [Get your Agent-readiness score](https://buildwithfern.com/agent-score) + +[Customers](https://buildwithfern.com/customers) [Blog](https://buildwithfern.com/blog) [Pricing](https://buildwithfern.com/pricing) + +[Start for free](https://dashboard.buildwithfern.com/sign-up?redirect_on_login=%2Fget-started&utm_source=fern-website&utm_content=nav-mobile-footer) \ No newline at end of file diff --git a/.codex/skills/svg-animations/SKILL.md b/.codex/skills/svg-animations/SKILL.md new file mode 100644 index 0000000..ed5a409 --- /dev/null +++ b/.codex/skills/svg-animations/SKILL.md @@ -0,0 +1,421 @@ +--- +name: svg-animations +description: Create beautiful, performant SVG animations and illustrations. Use this skill when the user asks to create SVG graphics, icons, illustrations, animated logos, path animations, morphing shapes, loading spinners, or any animated SVG content. Covers SMIL animations, CSS-driven SVG animation, path drawing effects, shape morphing, motion paths, gradients, masks, and filters. +--- + +This skill guides creation of handcrafted SVG animations — from simple animated icons to complex multi-stage path animations. SVGs are a markup language for images; every element is a DOM node you can style, animate, and script. + +## SVG Fundamentals + +### Coordinate System +SVGs use a coordinate system defined by `viewBox="minX minY width height"`. The viewBox is your canvas — all coordinates are relative to it, making SVGs resolution-independent. + +```svg + + + +``` + +### Shape Primitives + +```svg + + + + + + +``` + +### The `` Element — The Power Tool + +The `d` attribute defines a path using commands. Uppercase = absolute, lowercase = relative. + +| Command | Purpose | Syntax | +|---------|---------|--------| +| M/m | Move to | `M x y` | +| L/l | Line to | `L x y` | +| H/h | Horizontal line | `H x` | +| V/v | Vertical line | `V y` | +| C/c | Cubic bézier | `C x1 y1, x2 y2, x y` | +| S/s | Smooth cubic bézier | `S x2 y2, x y` | +| Q/q | Quadratic bézier | `Q x1 y1, x y` | +| T/t | Smooth quadratic | `T x y` | +| A/a | Elliptical arc | `A rx ry rotation large-arc sweep x y` | +| Z/z | Close path | `Z` | + +**Cubic Bézier** (`C`): Two control points define the curve. The first control point sets the departure angle, the second sets the arrival angle. + +```svg + +``` + +**Smooth Cubic** (`S`): Reflects the previous control point automatically — perfect for chaining fluid S-curves. + +```svg + +``` + +**Arc** (`A`): `rx ry x-rotation large-arc-flag sweep-flag x y` +- `large-arc-flag`: 0 = small arc, 1 = large arc (>180°) +- `sweep-flag`: 0 = counterclockwise, 1 = clockwise + +```svg + + +``` + +### Grouping and Transforms + +```svg + + + +``` + +Use `` to group elements for collective transforms, styling, and animation targets. + +### Gradients, Masks, and Filters + +```svg + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +--- + +## CSS Animations on SVG + +Many SVG attributes are valid CSS properties: `fill`, `stroke`, `opacity`, `transform`, `stroke-dasharray`, `stroke-dashoffset`, etc. + +### Basic CSS Animation + +```css +.pulse { + animation: pulse 2s ease-in-out infinite; + transform-origin: center; +} +@keyframes pulse { + 0%, 100% { transform: scale(1); opacity: 1; } + 50% { transform: scale(1.15); opacity: 0.7; } +} +``` + +### Stroke Drawing Animation (The Classic) + +The most iconic SVG animation. Uses `stroke-dasharray` and `stroke-dashoffset` to make a path appear to draw itself. + +**How it works:** +1. Set `stroke-dasharray` to the path's total length (one giant dash + one giant gap) +2. Set `stroke-dashoffset` to the same length (shifts the dash off-screen) +3. Animate `stroke-dashoffset` to 0 (slides the dash into view) + +```svg + + + + + +``` + +**Getting exact path length in JS:** +```js +const path = document.querySelector('.draw'); +const length = path.getTotalLength(); +path.style.strokeDasharray = length; +path.style.strokeDashoffset = length; +``` + +### Staggered Multi-Path Drawing + +```css +.line-1 { animation-delay: 0s; } +.line-2 { animation-delay: 0.3s; } +.line-3 { animation-delay: 0.6s; } +``` + +### CSS `d` Property Animation + +Modern browsers support animating the `d` attribute directly in CSS: + +```css +path { + d: path("M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30 Q 90,60 50,90 Q 10,60 10,30 z"); + transition: d 0.5s ease; +} +path:hover { + d: path("M 10,50 A 20,20 0,0,1 50,10 A 20,20 0,0,1 90,50 Q 90,80 50,100 Q 10,80 10,50 z"); +} +``` + +**Requirement:** Both paths must have the same number and types of commands for interpolation to work. + +--- + +## SMIL Animations (Native SVG) + +SMIL animations are declared directly inside SVG markup. They work even when SVG is loaded as an `` or CSS `background-image` — where CSS and JS can't reach. + +### `` — Animate Any Attribute + +```svg + + + +``` + +With keyframes: +```svg + +``` + +### `` — Transform Animations + +```svg + + + +``` + +Types: `translate`, `scale`, `rotate`, `skewX`, `skewY` + +### `` — Move Along a Path + +```svg + + + + + + +``` + +`rotate="auto"` orients the element tangent to the path. `rotate="auto-reverse"` flips it 180°. + +### `` — Discrete Value Changes + +```svg + + + +``` + +### Timing and Synchronization + +```svg + + + + +``` + +Trigger values: +- `begin="click"` — on click +- `begin="2s"` — after 2 seconds +- `begin="other.end"` — when another animation ends +- `begin="other.end + 1s"` — 1s after another ends +- `begin="other.repeat(2)"` — on 2nd repeat of another + +### Easing with `calcMode` and `keySplines` + +```svg + +``` + +`calcMode` options: `linear` (default), `discrete`, `paced`, `spline` + +`keySplines` takes cubic-bezier control points (x1 y1 x2 y2) per interval. Common easings: +- Ease-in-out: `0.42 0 0.58 1` +- Ease-out: `0 0 0.58 1` +- Bounce-ish: `0.34 1.56 0.64 1` + +### Shape Morphing with SMIL + +Both shapes must have identical command structures (same number of points, same command types): + +```svg + + + +``` + +--- + +## Animation Patterns & Recipes + +### Loading Spinner + +```svg + + + + + + +``` + +### Animated Checkmark + +```svg + + + + +``` + +### Morphing Hamburger to X + +```svg + + + + + + + + + + + +``` + +### Gradient Animation (Color Shift) + +```svg + + + + + + + + + + + +``` + +### Breathing / Pulsing Glow + +```svg + + + + +``` + +### Wave / Liquid Effect + +```svg + + + +``` + +--- + +## Best Practices + +1. **Use `viewBox`, never hardcode `width`/`height` in the SVG** — let the container size it. This keeps it resolution-independent. + +2. **`` for reusable definitions** — gradients, filters, masks, clipPaths, and reusable shapes belong in ``. + +3. **Prefer SMIL for self-contained SVGs** (icons, logos loaded via ``) — CSS/JS won't work there. Use CSS animations when the SVG is inlined and you want to coordinate with the rest of the page. + +4. **Shape morphing requires matching commands** — same number of path commands, same types, same order. If shapes differ, add invisible intermediate points to equalize. + +5. **`stroke-linecap="round"`** makes line animations look polished. + +6. **`fill="freeze"`** in SMIL keeps the final animation state. Without it, the element snaps back. + +7. **`transform-origin: center`** in CSS — SVG transforms default to the origin (0,0), not the element center. Always set this explicitly. + +8. **Use `getTotalLength()`** in JS to get exact path lengths for stroke animations instead of guessing. + +9. **Layer animations with `` groups** — animate the group transform separately from individual element properties for complex choreography. + +10. **Performance:** SVG animations are GPU-composited when animating `transform` and `opacity`. Animating `d`, `points`, or layout attributes triggers repaints — use sparingly on complex SVGs. + +11. **`will-change: transform`** on animated SVG elements helps the browser optimize compositing. + +12. **Accessibility:** Add `role="img"` and `` / `<desc>` elements. Use `prefers-reduced-motion` media query to disable animations for users who request it: + +```css +@media (prefers-reduced-motion: reduce) { + svg * { + animation: none !important; + transition: none !important; + } +} +``` diff --git a/.design-research/blackbox-docs-link-graph.md b/.design-research/blackbox-docs-link-graph.md new file mode 100644 index 0000000..6ba6554 --- /dev/null +++ b/.design-research/blackbox-docs-link-graph.md @@ -0,0 +1,262 @@ +# Blackbox Docs Link Graph + +Generated by `pnpm docs:graph` from the public Blackbox sidebar and internal Markdown/MDX links. + +- Source docs: `src/content/docs/blackbox` +- Public IA source: `src/config/sidebar.ts` +- Public pages: 27 +- Internal public links: 82 + +## Graph + +```mermaid +flowchart LR + subgraph Overview["Overview"] + overview_what_is_blackbox["What is Blackbox?"] + overview_how_blackbox_works["How Blackbox Works"] + end + subgraph Get_Started["Get Started"] + quickstart_installation["Installation"] + quickstart["Get Started"] + end + subgraph Concepts["Concepts"] + concepts_testing_layers["System and E2E Verification"] + concepts_human_agent_verification_loop["Human-Agent Verification Loop"] + concepts_effects_and_catalogs["OpenTelemetry and Effects"] + concepts_runtime_evidence["Runtime Evidence Boundaries"] + guides_playwright_isolation_and_parallelism["Test Topologies and Evidence Boundaries"] + concepts_omc_dc_coverage["Observable Decision Coverage"] + end + subgraph Guides["Guides"] + quickstart_build_behavioral_harness["Start From an Existing System or E2E Test"] + guides_set_up_the_testbed["Choose How Blackbox Runs Your Application"] + guides_use_existing_compose["Use an Existing Compose Project"] + guides_use_custom_provisioner["Use Testcontainers"] + guides_build_from_dockerfile["Build From a Dockerfile"] + guides_run_compiled_artifacts["Run Distribution Artifacts"] + guides_agent_skills["Repair a Blackbox Finding With a Coding Agent"] + guides_reports_and_ci_gates["Add Blackbox to CI"] + end + subgraph Reference["Reference"] + reference["Reference"] + reference_plans_and_realizations["Plans, Alignment, and Identity"] + reference_capture_lifecycle_and_correlation["Capture Lifecycle and Evidence"] + reference_effects_and_claims["Effects and Claim Evaluation"] + reference_observable_decision_coverage["Observable Decision Coverage Results"] + reference_cli["CLI and Exit Behavior"] + guides_source_maps_and_service_layouts["Source Maps and Service Layouts"] + operations_security_and_data_handling["Security and Data Handling"] + end + subgraph Status["Status"] + status_alpha_status["Alpha Support Status"] + end + concepts_effects_and_catalogs --> concepts_runtime_evidence + concepts_effects_and_catalogs --> reference_effects_and_claims + concepts_human_agent_verification_loop --> concepts_effects_and_catalogs + concepts_human_agent_verification_loop --> guides_agent_skills + concepts_human_agent_verification_loop --> reference_plans_and_realizations + concepts_omc_dc_coverage --> reference_effects_and_claims + concepts_omc_dc_coverage --> reference_observable_decision_coverage + concepts_runtime_evidence --> concepts_omc_dc_coverage + concepts_runtime_evidence --> operations_security_and_data_handling + concepts_runtime_evidence --> reference_capture_lifecycle_and_correlation + concepts_runtime_evidence --> reference_effects_and_claims + concepts_testing_layers --> concepts_human_agent_verification_loop + concepts_testing_layers --> overview_how_blackbox_works + guides_agent_skills --> concepts_human_agent_verification_loop + guides_agent_skills --> guides_reports_and_ci_gates + guides_agent_skills --> reference_cli + guides_build_from_dockerfile --> guides_use_existing_compose + guides_build_from_dockerfile --> status_alpha_status + guides_playwright_isolation_and_parallelism --> concepts_omc_dc_coverage + guides_playwright_isolation_and_parallelism --> reference_capture_lifecycle_and_correlation + guides_reports_and_ci_gates --> operations_security_and_data_handling + guides_reports_and_ci_gates --> reference_cli + guides_run_compiled_artifacts --> guides_use_existing_compose + guides_run_compiled_artifacts --> status_alpha_status + guides_set_up_the_testbed --> guides_build_from_dockerfile + guides_set_up_the_testbed --> guides_run_compiled_artifacts + guides_set_up_the_testbed --> guides_use_custom_provisioner + guides_set_up_the_testbed --> guides_use_existing_compose + guides_set_up_the_testbed --> status_alpha_status + guides_source_maps_and_service_layouts --> reference_plans_and_realizations + guides_source_maps_and_service_layouts --> status_alpha_status + guides_use_custom_provisioner --> guides_playwright_isolation_and_parallelism + guides_use_custom_provisioner --> status_alpha_status + guides_use_existing_compose --> concepts_runtime_evidence + guides_use_existing_compose --> guides_playwright_isolation_and_parallelism + guides_use_existing_compose --> quickstart + guides_use_existing_compose --> quickstart_build_behavioral_harness + operations_security_and_data_handling --> reference_capture_lifecycle_and_correlation + operations_security_and_data_handling --> status_alpha_status + overview_how_blackbox_works --> concepts_human_agent_verification_loop + overview_how_blackbox_works --> concepts_omc_dc_coverage + overview_how_blackbox_works --> concepts_runtime_evidence + overview_how_blackbox_works --> concepts_testing_layers + overview_how_blackbox_works --> reference_capture_lifecycle_and_correlation + overview_how_blackbox_works --> reference_effects_and_claims + overview_how_blackbox_works --> reference_plans_and_realizations + overview_what_is_blackbox --> concepts_effects_and_catalogs + overview_what_is_blackbox --> concepts_omc_dc_coverage + overview_what_is_blackbox --> overview_how_blackbox_works + overview_what_is_blackbox --> quickstart + quickstart --> guides_set_up_the_testbed + quickstart --> guides_use_existing_compose + quickstart --> overview_how_blackbox_works + quickstart --> quickstart_build_behavioral_harness + quickstart --> quickstart_installation + quickstart_build_behavioral_harness --> guides_set_up_the_testbed + quickstart_installation --> guides_set_up_the_testbed + quickstart_installation --> quickstart + quickstart_installation --> status_alpha_status + reference --> operations_security_and_data_handling + reference --> reference_capture_lifecycle_and_correlation + reference --> reference_cli + reference --> reference_effects_and_claims + reference --> reference_observable_decision_coverage + reference --> reference_plans_and_realizations + reference --> status_alpha_status + reference_capture_lifecycle_and_correlation --> concepts_runtime_evidence + reference_capture_lifecycle_and_correlation --> guides_playwright_isolation_and_parallelism + reference_capture_lifecycle_and_correlation --> reference_effects_and_claims + reference_cli --> guides_reports_and_ci_gates + reference_cli --> reference_effects_and_claims + reference_effects_and_claims --> concepts_effects_and_catalogs + reference_effects_and_claims --> concepts_runtime_evidence + reference_effects_and_claims --> reference_capture_lifecycle_and_correlation + reference_observable_decision_coverage --> concepts_omc_dc_coverage + reference_observable_decision_coverage --> reference_capture_lifecycle_and_correlation + reference_observable_decision_coverage --> reference_effects_and_claims + reference_plans_and_realizations --> concepts_human_agent_verification_loop + reference_plans_and_realizations --> guides_source_maps_and_service_layouts + reference_plans_and_realizations --> reference_capture_lifecycle_and_correlation + status_alpha_status --> guides_set_up_the_testbed + status_alpha_status --> quickstart +``` + +## What The Graph Says + +- The graph only includes pages that are present in the public Blackbox sidebar. +- Overview and Get Started pages should point readers into concepts, guides, and reference pages when they need depth. +- Reference pages may be sinks, but they should usually offer a next-step link back to a workflow. +- Pages with no incoming links are reachable from the sidebar, but not from the reading journey. That is a signal to add contextual links or confirm they are pure lookup pages. + +## Edges + +- OpenTelemetry and Effects + - Runtime Evidence Boundaries + - Effects and Claim Evaluation +- Human-Agent Verification Loop + - OpenTelemetry and Effects + - Repair a Blackbox Finding With a Coding Agent + - Plans, Alignment, and Identity +- Observable Decision Coverage + - Effects and Claim Evaluation + - Observable Decision Coverage Results +- Runtime Evidence Boundaries + - Observable Decision Coverage + - Security and Data Handling + - Capture Lifecycle and Evidence + - Effects and Claim Evaluation +- System and E2E Verification + - Human-Agent Verification Loop + - How Blackbox Works +- Repair a Blackbox Finding With a Coding Agent + - Human-Agent Verification Loop + - Add Blackbox to CI + - CLI and Exit Behavior +- Build From a Dockerfile + - Use an Existing Compose Project + - Alpha Support Status +- Test Topologies and Evidence Boundaries + - Observable Decision Coverage + - Capture Lifecycle and Evidence +- Add Blackbox to CI + - Security and Data Handling + - CLI and Exit Behavior +- Run Distribution Artifacts + - Use an Existing Compose Project + - Alpha Support Status +- Choose How Blackbox Runs Your Application + - Build From a Dockerfile + - Run Distribution Artifacts + - Use Testcontainers + - Use an Existing Compose Project + - Alpha Support Status +- Source Maps and Service Layouts + - Plans, Alignment, and Identity + - Alpha Support Status +- Use Testcontainers + - Test Topologies and Evidence Boundaries + - Alpha Support Status +- Use an Existing Compose Project + - Runtime Evidence Boundaries + - Test Topologies and Evidence Boundaries + - Get Started + - Start From an Existing System or E2E Test +- Security and Data Handling + - Capture Lifecycle and Evidence + - Alpha Support Status +- How Blackbox Works + - Human-Agent Verification Loop + - Observable Decision Coverage + - Runtime Evidence Boundaries + - System and E2E Verification + - Capture Lifecycle and Evidence + - Effects and Claim Evaluation + - Plans, Alignment, and Identity +- What is Blackbox? + - OpenTelemetry and Effects + - Observable Decision Coverage + - How Blackbox Works + - Get Started +- Get Started + - Choose How Blackbox Runs Your Application + - Use an Existing Compose Project + - How Blackbox Works + - Start From an Existing System or E2E Test + - Installation +- Start From an Existing System or E2E Test + - Choose How Blackbox Runs Your Application +- Installation + - Choose How Blackbox Runs Your Application + - Get Started + - Alpha Support Status +- Reference + - Security and Data Handling + - Capture Lifecycle and Evidence + - CLI and Exit Behavior + - Effects and Claim Evaluation + - Observable Decision Coverage Results + - Plans, Alignment, and Identity + - Alpha Support Status +- Capture Lifecycle and Evidence + - Runtime Evidence Boundaries + - Test Topologies and Evidence Boundaries + - Effects and Claim Evaluation +- CLI and Exit Behavior + - Add Blackbox to CI + - Effects and Claim Evaluation +- Effects and Claim Evaluation + - OpenTelemetry and Effects + - Runtime Evidence Boundaries + - Capture Lifecycle and Evidence +- Observable Decision Coverage Results + - Observable Decision Coverage + - Capture Lifecycle and Evidence + - Effects and Claim Evaluation +- Plans, Alignment, and Identity + - Human-Agent Verification Loop + - Source Maps and Service Layouts + - Capture Lifecycle and Evidence +- Alpha Support Status + - Choose How Blackbox Runs Your Application + - Get Started + +## No Outgoing Internal Links + + +## No Incoming Internal Links + +- What is Blackbox? (/docs/blackbox/overview/what-is-blackbox) +- Reference (/docs/blackbox/reference) diff --git a/.design-research/blackbox-public-setup-snippets-task.md b/.design-research/blackbox-public-setup-snippets-task.md new file mode 100644 index 0000000..5233453 --- /dev/null +++ b/.design-research/blackbox-public-setup-snippets-task.md @@ -0,0 +1,20 @@ +# Blackbox Public Setup Snippets Follow-up + +Status: blocked on certified public interfaces and package-boundary support. + +Replace the conceptual pseudocode in the four application-setup guides only after the corresponding public interface is certified and documented. + +- [ ] Replace the Compose execution placeholder with the published invocation. +- [ ] Replace the Testcontainers pseudocode with the certified integration API. +- [ ] Replace the Dockerfile pseudocode when Dockerfile input becomes supported. +- [ ] Replace the distribution-artifact pseudocode when artifact input becomes supported. +- [ ] Verify every snippet in a packed external consumer. +- [ ] Update Alpha Support Status without implying support for adjacent configurations. + +Affected pages: + +- `src/content/docs/blackbox/guides/set-up-the-testbed.mdx` +- `src/content/docs/blackbox/guides/use-existing-compose.mdx` +- `src/content/docs/blackbox/guides/use-custom-provisioner.mdx` +- `src/content/docs/blackbox/guides/build-from-dockerfile.mdx` +- `src/content/docs/blackbox/guides/run-compiled-artifacts.mdx` diff --git a/.design-research/blackbox-verification-loop-north-star.md b/.design-research/blackbox-verification-loop-north-star.md new file mode 100644 index 0000000..773576d --- /dev/null +++ b/.design-research/blackbox-verification-loop-north-star.md @@ -0,0 +1,50 @@ +# Blackbox Verification Loop North Star + +> Internal product reference for evaluating Blackbox features, documentation, demos, and agent workflows. + +Blackbox turns product intent into a human-approved behavioral contract and connects it to an executable system test. It combines the native test result with runtime evidence to return deterministic findings that keep coding agents repairing the implementation against that contract. + +## The Loop + +```mermaid +flowchart LR + I[Product intent] --> M{Authoring mode} + M --> G[Proposed Gherkin<br/>behavioral contract] + M --> D[Proposed direct<br/>behavioral contract] + G --> A{Person approves?} + D --> A + A -->|Revise| M + A -->|Approve contract| T[Agent builds the system test<br/>and implementation] + T --> R[Run the system test] + R --> O[OpenTelemetry<br/>observations] + R --> C[Assurance control<br/>facts] + O --> E[Seal eligible<br/>attempt evidence] + C --> E + E --> F[Blackbox returns a finding<br/>and one of five verdicts] + A -. Behavioral contract .-> F + F -->|Satisfied| V[Contract satisfied<br/>for this attempt] + F -->|Producer routes repair| X[Agent repairs the<br/>implementation] + F -->|Meaning change| A + X --> R +``` + +The person controls the behavioral contract. The agent may propose the contract, build tests, implement it, and repair failures, but it cannot change the approved contract to make a finding pass. + +## What Blackbox Adds + +- **Feedforward:** The approved behavioral contract guides the agent before it implements the behavior. +- **Runtime verification:** System-test assertions check the visible result. OpenTelemetry observations reveal supported system work, while separate control facts establish evidence eligibility and limits. +- **Deterministic feedback:** The same behavioral contract, declared test, analysis rules, and sealed evidence produce the same finding. +- **Verdict-aware repair:** Findings preserve the producer-owned repair owner, permitted actions, and earliest rerun gate. Changes to the behavioral contract return to a person for approval. + +## Non-Negotiable Boundaries + +- Every case attempt, retry, and sequence member receives fresh managed capture. +- Runtime effects are the core evidence used to verify behavior beyond the visible test result. Effects never create or change the behavioral contract. +- Incomplete evidence cannot prove absence, an upper bound, or an exact count. +- Observable Decision Coverage may add insight after effect analysis, but it cannot change an ordinary effect finding. +- A distributed system may behave differently between runs. Determinism applies to analysis of fixed inputs and evidence. + +## Product Test + +Every addition to Blackbox should strengthen at least one part of this loop: translating intent, approving the behavioral contract, realizing a test, capturing evidence, producing findings, or guiding repair. It must not weaken human authority over the contract. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d33754..9017c01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI +name: Website checks on: pull_request: @@ -6,25 +6,21 @@ on: branches: [master] jobs: - build: + website: runs-on: ubuntu-latest + strategy: + matrix: + context: [production, deploy-preview] steps: - uses: actions/checkout@v4 - + - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - cache: yarn - - - name: Install dependencies - run: yarn install --frozen-lockfile - - # docusaurus.config.ts sets onBrokenLinks: "throw", so this fails the - # job on any broken internal link. Without it that failure only shows - # up in Netlify after the merge has already landed. - # - # CONTEXT is unset here, so the build omits the analytics tag. The - # Algolia reindex is not run: it is a no-op outside a production - # deploy and CI has no crawler credentials. - - name: Build - run: yarn build + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm check + - run: pnpm test + - run: pnpm build + env: + CONTEXT: ${{ matrix.context }} diff --git a/.gitignore b/.gitignore index 124237e..5e6fec1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,29 @@ -node_modules -.idea -.docusaurus -build +# build output +dist/ +.vercel/ -# Local Netlify folder -.netlify +# generated types +.astro/ -# macOS +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# environment variables +.env +.env.production +.env.local + +# macOS-specific files .DS_Store + +# jetbrains setting folder +.idea/ +.claude +.mcp.json +.npmrc diff --git a/.scratch/ui-ux/_merged.css b/.scratch/ui-ux/_merged.css new file mode 100644 index 0000000..5ec5f84 --- /dev/null +++ b/.scratch/ui-ux/_merged.css @@ -0,0 +1,1261 @@ +/* === site-chrome === */ +/* Site chrome polish: focus rings, hover transitions, mobile touch targets, + footer link affordances. All changes reuse existing tokens. */ + +/* APPEND-TO: .brand (components.css L96) */ +/* Add an explicit focus-visible radius so the global 2px outline wraps + cleanly around the inline-flex brand bounding box instead of clipping + into the mark and product slug. */ +.brand { + border-radius: 10px; +} + +/* NEW */ +/* Match the focus ring radius to the brand's own corner so the ring + visually hugs the element on keyboard focus. */ +.brand:focus-visible { + outline-offset: 4px; +} + +/* REPLACES: components.css L1123-L1131 */ +/* Icon-only header action (GitHub). Adds smooth hover transition for + color and background per ui-ux-pro-max duration-timing rule (200ms). + Global @media (prefers-reduced-motion: reduce) zeroes this transition. */ +.icon-link { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + color: var(--text-muted); + border-radius: 8px; + transition: color 200ms ease, background-color 200ms ease; +} + +/* APPEND-TO: .nav-action (components.css L1148) */ +/* Smooth hover transition for the Examples / Sponsor text actions. */ +.nav-action { + border-radius: 6px; + transition: color 200ms ease; +} + +/* APPEND-TO: .nav-tabs a (components.css L57) */ +/* Smooth color + underline transition when switching active product tab. */ +.nav-tabs a { + transition: color 200ms ease, border-bottom-color 200ms ease; +} + +/* APPEND-TO: .navlinks a (implicit, components.css L131) */ +/* Smooth fade between muted and primary text colors on navlink hover. */ +.navlinks a { + transition: color 200ms ease; +} + +/* NEW */ +/* Mobile touch target floor for header text actions and product tabs. + ui-ux-pro-max touch-target-size requires minimum 44x44px. The existing + 767px breakpoint at the bottom of components.css already handles + .btn-sm, .tab, and .icon-link. Extending coverage to .nav-action and + .nav-tabs a so every header touch target meets the floor. */ +@media (max-width: 767px) { + .nav-action { + min-height: 44px; + display: inline-flex; + align-items: center; + padding: 8px 10px; + } + .nav-tabs a { + min-height: 44px; + display: inline-flex; + align-items: center; + padding: 10px 0; + } +} + +/* NEW */ +/* Footer link affordance. The Footer template renders + <a class="site-footer-link"> for the author name; without an explicit + rule it inherits the global link color (primary) and has no hover or + focus feedback. Mirror the navlinks pattern: muted at rest, full text + on hover, smooth color transition, rounded focus ring. */ +.site-footer-link { + color: var(--text); + text-decoration: underline; + text-decoration-color: var(--border-strong); + text-underline-offset: 3px; + border-radius: 4px; + transition: color 200ms ease, text-decoration-color 200ms ease; +} +.site-footer-link:hover { + color: var(--primary-light); + text-decoration-color: var(--primary-border); +} + +/* NEW */ +/* The Footer icon-link sits in a flex row alongside two <p> tags. The + site-footer-inner uses space-between, so the GitHub anchor naturally + anchors left. On focus, give it a slightly larger offset so the ring + does not collide with the surrounding text baseline. */ +.site-footer .icon-link:focus-visible { + outline-offset: 3px; +} + + +/* === docs-chrome === */ +/* Audit and fixes for sidebar, TOC, and prev/next pager. + Pairs with edits to: + src/components/docs/Sidebar.astro (wraps groups in <nav>, items in <ul>/<li>) + src/components/docs/TOC.astro (adds role="list", aria-current="location") + src/components/docs/PrevNext.astro (drops .btn, adds eyebrow + label spans) */ + +/* ---------- Sidebar ---------------------------------------------------- */ + +/* NEW: outer landmark wrapper now rendered by Sidebar.astro. + Keep zero chrome so layout stays identical to the old bare-details list. */ +.sidebar-nav { + display: block; +} + +/* APPEND-TO: .sidebar a (components.css L790) + Slightly tighter focus offset so the global 2px ring tucks against the link + instead of bleeding into the adjacent structure-line. */ +.sidebar a:focus-visible { + outline-offset: -2px; +} + +/* REPLACES: components.css L1249-L1258 + .sidebar-group-items was a <div>; it is now a <ul role="list">. Reset list + chrome (padding/margin/list-style) and keep the existing flex layout and + structure-line. li children get zero margin so spacing comes from the + gap on the flex parent. */ +.sidebar-group-items { + list-style: none; + display: flex; + flex-direction: column; + gap: 2px; + padding: 4px 0 0; + margin: 0 0 0 14px; + padding-left: 12px; + border-left: 1px solid var(--border); + animation: sidebar-group-fade-in 180ms ease-out; +} + +/* NEW: li children inside the new <ul> wrapper. */ +.sidebar-group-items > li { + margin: 0; + padding: 0; + min-width: 0; +} + +/* APPEND-TO: .sidebar a (components.css L790) + Long labels (e.g. "Install Suites for NestJS, Inversify, Jest, Vitest...") + must wrap inside the 268px sidebar instead of pushing the column wider. */ +.sidebar a { + min-width: 0; + overflow-wrap: anywhere; +} + +/* ---------- TOC -------------------------------------------------------- */ + +/* APPEND-TO: .toc-list a.active (components.css L961) + Mirror the .active class style for aria-current="location" so the script's + ARIA update alone is enough if the class is ever dropped. */ +.toc-list a[aria-current="location"] { + color: var(--primary-light); + border-left-color: var(--primary); + font-weight: 600; +} + +/* APPEND-TO: .toc-list a:focus-visible (components.css L971) + Pull the global 2px ring inward so it does not visually merge with the + 2px left-border bar used for the active indicator. */ +.toc-list a:focus-visible { + outline-offset: -2px; +} + +/* ---------- Prev / Next pager ----------------------------------------- */ + +/* REPLACES: components.css L1277-L1285 + Add min-width:0 propagation so flex children can shrink under long labels + without overflowing the article column. */ +.doc-pager { + display: flex; + justify-content: space-between; + align-items: stretch; + gap: 16px; + margin-top: 48px; + padding-top: 24px; + border-top: 1px solid var(--border); + min-width: 0; +} + +/* REPLACES: components.css L1287-L1299 + The pager anchors no longer inherit .btn (pill, 42px min-height). Restate + the full box here so the cards keep their card-style border/background and + gain min-width:0 + overflow-wrap so long titles wrap to two lines. */ +.doc-pager a { + flex: 1 1 0; + min-width: 0; + display: flex; + flex-direction: column; + gap: 4px; + padding: 14px 18px; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--text-muted); + background: rgba(255,255,255,.02); + text-decoration: none; + transition: border-color 200ms ease-out, color 200ms ease-out, background 200ms ease-out; +} + +/* APPEND-TO: .doc-pager a:hover (components.css L1301) */ +.doc-pager a:hover { + border-color: var(--primary-border); +} + +/* REPLACES: components.css L1307-L1313 + The first child is now .doc-pager-eyebrow (a span containing the arrow + plus the word "Previous"/"Next"). Style the eyebrow itself, not whatever + span happened to be first. */ +.doc-pager-eyebrow { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 12px; + font-weight: 600; + color: var(--text-soft); + text-transform: uppercase; + letter-spacing: 0.06em; + line-height: 1; +} + +/* REPLACES: components.css L1315-L1319 + The label is now .doc-pager-label. Allow it to wrap to two lines on + narrow viewports without forcing a column-stretch. */ +.doc-pager-label { + display: block; + font-size: 15px; + font-weight: 700; + color: var(--text); + line-height: 1.35; + overflow-wrap: anywhere; + /* Clamp very long titles to two lines on desktop. Falls back to natural + wrap where line-clamp isn't supported. */ + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* NEW: arrow glyph inside the eyebrow. */ +.doc-pager-arrow { + font-size: 14px; + line-height: 1; + color: var(--text-muted); + transition: transform 180ms ease-out, color 180ms ease-out; +} + +.doc-pager a:hover .doc-pager-arrow { + color: var(--primary-light); +} + +.doc-pager-prev:hover .doc-pager-arrow { transform: translateX(-2px); } +.doc-pager-next:hover .doc-pager-arrow { transform: translateX(2px); } + +@media (prefers-reduced-motion: reduce) { + .doc-pager-arrow { transition: none; } + .doc-pager-prev:hover .doc-pager-arrow, + .doc-pager-next:hover .doc-pager-arrow { transform: none; } +} + +/* NEW: empty side placeholder keeps the grid balanced when only prev or + only next exists. Mirrors a card's outer footprint but stays invisible. */ +.doc-pager-spacer { + flex: 1 1 0; + min-width: 0; +} + +/* APPEND-TO: .doc-pager-prev / .doc-pager-next (components.css L1321-L1322) + Re-state alignment now that .btn is no longer applied. */ +.doc-pager-prev { text-align: left; align-items: flex-start; } +.doc-pager-next { text-align: right; align-items: flex-end; } + +/* Focus ring sits inside the rounded border. */ +.doc-pager a:focus-visible { + outline-offset: -2px; + border-radius: var(--radius-sm); +} + +/* APPEND-TO: @media (max-width: 640px) .doc-pager (components.css L1324) + Touch-target floor: pager cards stack on small screens. Ensure each card + clears the 44x44 minimum without inflating desktop padding. */ +@media (max-width: 640px) { + .doc-pager a { min-height: 44px; } + .doc-pager-label { -webkit-line-clamp: 3; } +} + +/* TOKEN PROPOSALS */ +/* None. All values reuse existing tokens in tokens.css. */ + + +/* === ui-primitives === */ +/* Surgical UX hardening for buttons, cards, alerts, badges, chips, grids. + No new variants. Preserves component APIs. Respects prefers-reduced-motion. + References: + - WCAG 2.1 AA contrast, focus-visible + - ui-ux-pro-max: focus-states, disabled-states, loading-states, + hover-states, cursor-pointer, reduced-motion, touch-target-size, + content-jumping (no transform/scale hover that shifts layout) */ + +/* --------------------------------------------------------------------------- + BUTTONS + --------------------------------------------------------------------------- */ + +/* REPLACES: components.css L372-385 + - Adds a smooth color/border/shadow transition so hover does not snap. + - Adds default `vertical-align: middle` so inline btns sit cleanly next to text. + - Adds `user-select: none` and `text-decoration: none` (anchors). */ +.btn { + display: inline-flex; + min-height: 42px; + align-items: center; + justify-content: center; + gap: 8px; + padding: 0 18px; + border-radius: 999px; + border: 1px solid var(--border-strong); + background: rgba(255, 255, 255, .04); + color: var(--text); + font-size: 14px; + font-weight: 700; + text-decoration: none; + user-select: none; + vertical-align: middle; + transition: background-color 160ms ease-out, border-color 160ms ease-out, + color 160ms ease-out, box-shadow 160ms ease-out; +} + +/* NEW + Generic hover for secondary/ghost/danger. Primary keeps its own rule below. + Uses color/border only (no transform) so layout never shifts. */ +.btn:hover { + background: rgba(255, 255, 255, .07); + border-color: var(--border-strong); + color: var(--text); +} + +/* NEW + Distinct focus-visible ring tuned for pink-on-dark. + Globals apply a 2px var(--primary) outline; on .btn-primary that ring would + blend into the pink fill. Use a layered ring: 2px dark gap + 2px pink halo. */ +.btn:focus-visible { + outline: none; + box-shadow: + 0 0 0 2px var(--bg), + 0 0 0 4px var(--primary-light); +} + +/* NEW + Pressed-state feedback. Color shift only, no scale (avoids content jump). */ +.btn:active:not(:disabled):not([aria-disabled="true"]) { + background: rgba(255, 255, 255, .09); +} + +/* REPLACES: components.css L387-394 + - Keeps the pink fill and shadow. + - Tightens hover: brighten + slightly stronger shadow. + - Removes default text-shadow inheritance from page. */ +.btn-primary { + background: var(--primary); + border-color: var(--primary); + color: #0F0F12; + box-shadow: 0 14px 38px rgba(239, 71, 121, .26); +} +.btn-primary:hover { + background: var(--primary-light); + border-color: var(--primary-light); + color: #0F0F12; + box-shadow: 0 14px 38px rgba(239, 71, 121, .34); +} +.btn-primary:active:not(:disabled):not([aria-disabled="true"]) { + background: var(--primary); + border-color: var(--primary); + box-shadow: 0 10px 28px rgba(239, 71, 121, .28); +} + +/* APPEND-TO: .btn-secondary (components.css L396-398) + Hover surfaces lift to --surface-hover so the affordance is unambiguous. */ +.btn-secondary:hover { + background: var(--surface-hover); + border-color: var(--border-strong); +} + +/* APPEND-TO: .btn-ghost (components.css L400-402) + Ghost needs a visible hover or it looks dead. */ +.btn-ghost:hover { + background: rgba(255, 255, 255, .05); +} + +/* APPEND-TO: .btn-danger (components.css L404-408) */ +.btn-danger:hover { + background: rgba(255, 107, 107, .16); + border-color: rgba(255, 107, 107, .42); + color: #FFD4D4; +} + +/* REPLACES: components.css L1120 + Disabled visual state for both <button disabled> and <a aria-disabled="true">. + Loses pointer affordance, dims, and kills hover/shadow transitions. */ +.btn:disabled, +.btn[aria-disabled="true"], +.tab:disabled, +.tab[aria-disabled="true"] { + cursor: not-allowed; + opacity: 0.55; + box-shadow: none; + pointer-events: none; +} + +/* NEW + Loading-state convention. Replace label with an inline spinner without + resizing the button. The button keeps its width via `position: relative` + on the host and an absolutely-positioned spinner. We hide the slot text + visually (kept in DOM for SR + aria-busy announces in-flight state). */ +.btn-loading { + position: relative; + cursor: progress; + pointer-events: none; + color: transparent !important; +} +.btn-loading::after { + content: ""; + position: absolute; + width: 16px; + height: 16px; + border-radius: 50%; + border: 2px solid currentColor; + border-top-color: transparent; + color: var(--text); + animation: btn-spin 720ms linear infinite; +} +.btn-primary.btn-loading::after { color: #0F0F12; } +.btn-danger.btn-loading::after { color: #FFB4B4; } + +@keyframes btn-spin { + to { transform: rotate(360deg); } +} + +@media (prefers-reduced-motion: reduce) { + .btn { transition: none; } + .btn-loading::after { animation-duration: 1800ms; } +} + +/* --------------------------------------------------------------------------- + CARDS + --------------------------------------------------------------------------- */ + +/* APPEND-TO: .card (components.css L232-238) + Adds default color + transition so an interactive card can animate cleanly + without changing static-card visuals. */ +.card { + color: inherit; + transition: background-color 180ms ease-out, border-color 180ms ease-out, + box-shadow 180ms ease-out; +} + +/* NEW + Interactive card variant (used when Card is rendered as <a> or with + `interactive` prop. Provides cursor, hover, and focus affordance without + layout shift (color/border/shadow only, no scale). */ +.card-interactive { + cursor: pointer; + text-decoration: none; +} +.card-interactive:hover { + background: rgba(255, 255, 255, .055); + border-color: var(--border-strong); + box-shadow: 0 10px 30px rgba(0, 0, 0, .28); +} +.card-interactive:focus-visible { + outline: none; + border-color: var(--primary-border); + box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--primary-light); +} + +@media (prefers-reduced-motion: reduce) { + .card { transition: none; } +} + +/* --------------------------------------------------------------------------- + ALERTS + --------------------------------------------------------------------------- */ + +/* APPEND-TO: .alert (components.css L464-472) + Alert link focus alignment. The body anchor uses a border-bottom underline; + on focus-visible promote the underline to the brand color so keyboard users + see exactly which inline link is active without the global pink outline + clashing with the surrounding card. */ +.alert-body a:focus-visible { + outline: none; + border-bottom-color: var(--primary); + background: var(--primary-soft); + border-radius: 2px; +} + +/* --------------------------------------------------------------------------- + BADGES & CHIPS + --------------------------------------------------------------------------- */ + +/* REPLACES: components.css L417-428 + - Default badge text was `var(--text-muted)` (#A1A1AA) on a near-bg surface. + Bumps to a slightly stronger value to clear 4.5:1 against the dark canvas + even when stacked on the section gradient. + - Adds line-height + padding-y to give breathing room for taller glyphs. */ +.badge { + display: inline-flex; + min-height: 28px; + align-items: center; + border: 1px solid var(--border); + background: rgba(255, 255, 255, .045); + border-radius: 999px; + padding: 2px 10px; + font-size: 12px; + font-weight: 720; + line-height: 1.4; + color: var(--text); + letter-spacing: 0.01em; +} + +/* APPEND-TO: .chip (components.css L207-220) + Small line-height + vertical padding tweak so chips sit centered alongside + buttons in a btn-row without visual drift. */ +.chip { + line-height: 1.4; + padding-block: 2px; +} + +/* --------------------------------------------------------------------------- + GRID + --------------------------------------------------------------------------- */ + +/* APPEND-TO: .grid (components.css L222-225) + Hard `repeat(N, 1fr)` breaks below ~640px (text gets squeezed below 16px). + Collapse 3/4/5 columns to 2 at <=768px and to 1 at <=520px. Keeps cols-2 + at >=520px. Logical (not transform) layout, no content jump on resize. */ +@media (max-width: 768px) { + .grid.cols-3, + .grid.cols-4, + .grid.cols-5 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} +@media (max-width: 520px) { + .grid.cols-2, + .grid.cols-3, + .grid.cols-4, + .grid.cols-5 { + grid-template-columns: minmax(0, 1fr); + } +} + +/* --------------------------------------------------------------------------- + SECTION + HERO + --------------------------------------------------------------------------- */ + +/* APPEND-TO: .section (components.css L172-181) + Reduce horizontal padding on small screens so sections do not bleed past + container gutters. No vertical-rhythm change. */ +@media (max-width: 640px) { + .section { + padding: 24px 18px; + border-radius: var(--radius-lg); + } +} + +/* APPEND-TO: .hero (components.css L133-138) + Hero top padding is large for desktop. Tighten on small screens so the + first interactive control stays near the fold. */ +@media (max-width: 640px) { + .hero { + padding: 56px 0 36px; + } +} + +/* --------------------------------------------------------------------------- + FORMS (.field) + --------------------------------------------------------------------------- */ + +/* APPEND-TO: input/select/textarea focus (components.css L459-462) + The custom inset focus halo already provides a strong indicator; ensure the + default browser outline stays suppressed on focus-visible too (globals.css + already does this, but reinforce locally so future global resets do not + double-ring the field). */ +.field :is(input, select, textarea):focus-visible { + outline: none; +} + +/* =========================================================================== + TOKEN PROPOSALS (do not apply, for review against tokens.css upstream) + =========================================================================== + 1. --focus-ring-offset-color: var(--bg) + Used by the layered focus ring (gap-then-halo). Today inlined as var(--bg) + in .btn:focus-visible and .card-interactive:focus-visible. + 2. --transition-fast: 160ms ease-out + Used by .btn, .card transitions. A token would let us tune system-wide + motion without searching files. + 3. --shadow-btn-primary: 0 14px 38px rgba(239,71,121,.26) + Currently inlined in .btn-primary; a token would harmonize with the + pricing-card featured glow which uses a similar pink shadow. + =========================================================================== */ + + +/* === mdx === */ +/* Surgical deltas for src/components/mdx/* (Aside, Tabs, TabItem, MermaidDiagram). + All selectors here are inside the documented scope: .aside*, .tabs, .tab, + .tab.active. Mermaid additions use a fresh .mermaid-* namespace introduced + by the component itself. */ + + +/* ---------- Aside ---------------------------------------------------------- */ + +/* APPEND-TO: .aside (components.css L541-L549) + Tighten left-rule paint when role="note" is announced (no change in look) + and add a subtle variant tint so the four severity colors are visually + distinct on the body, not only on the icon tile. Tint alphas chosen so + --text-muted (#A1A1AA, 8.4:1 on #0F0F12) keeps >= 4.5:1 contrast on top. */ +.aside { isolation: isolate; } + +/* APPEND-TO: .aside-info / .aside-success / .aside-warning / .aside-error + (components.css L616-L619) + Add a faint tinted wash to the card surface. The left-border accent and + icon tile colors are already set; this only changes the background. */ +.aside-info { background: linear-gradient(to right, rgba(147,197,253,.045), rgba(255,255,255,.03) 40%); } +.aside-success { background: linear-gradient(to right, rgba(99,230,190,.045), rgba(255,255,255,.03) 40%); } +.aside-warning { background: linear-gradient(to right, rgba(255,209,102,.045), rgba(255,255,255,.03) 40%); } +.aside-error { background: linear-gradient(to right, rgba(255,107,107,.05), rgba(255,255,255,.03) 40%); } + +/* APPEND-TO: .aside-title (components.css L572-L580) + <strong> is bold by default; reset and rely on the token weight. Keep the + element semantically a strong-emphasis label rather than a heading so the + article outline is not polluted by every Aside title. */ +.aside-title { font-style: normal; font-weight: 720; } + + +/* ---------- Tabs ----------------------------------------------------------- */ + +/* REPLACES: components.css L655-L662 (.tabs) + Add flex-wrap so the rail doesn't horizontally overflow the 72ch article + column on narrow viewports, and a row-gap so wrapped pills line up cleanly. */ +.tabs { + display: inline-flex; + flex-wrap: wrap; + row-gap: 3px; + padding: 4px; + border: 1px solid var(--border); + border-radius: 999px; + background: rgba(255,255,255,.03); + gap: 3px; + max-width: 100%; +} + +/* REPLACES: components.css L664-L670 (.tab) + Add background:transparent (so inactive tabs paint over the rail), color + transition for smooth selection, and an explicit border:0 reset since + buttons inherit UA borders. Cursor and disabled state are inherited from + the existing .btn, .tab, [role="button"] block at L1119-L1120. */ +.tab { + border: 0; + background: transparent; + border-radius: 999px; + color: var(--text-muted); + font-size: 13px; + font-weight: 700; + padding: 8px 13px; + transition: color 180ms ease, background-color 180ms ease, box-shadow 180ms ease; +} + +/* NEW: hover affordance for inactive tabs. + Active state already paints color + soft pink; this is just a hint that + the inactive label is interactive. */ +.tab:hover:not(.active) { + color: var(--text); + background: rgba(255,255,255,.04); +} + +/* NEW: visible keyboard focus ring on the tab itself. + Global :focus-visible at components.css L1110-L1116 already paints an + outline on every focusable element, but tabs sit on a tinted background + so we bump contrast with an inset ring that respects the brand color. */ +.tab:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 2px; +} + +/* NEW: focus ring on the tabpanel so screen-reader/keyboard users see where + focus has landed after activating a tab. The panel is tabindex=0 so it can + receive focus and be scrolled by the user with arrow keys. */ +.tabs-block [role="tabpanel"]:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 4px; + border-radius: var(--radius-xs, 10px); +} + +/* NEW: respect reduced motion for the tab transition. */ +@media (prefers-reduced-motion: reduce) { + .tab { transition: none; } +} + + +/* ---------- Mermaid -------------------------------------------------------- */ +/* NEW: .mermaid-* namespace introduced by MermaidDiagram.tsx. Article column + is 72ch (~720px) so we let the diagram fill width and scroll horizontally + only when its intrinsic width truly exceeds the column. */ +.mermaid-figure { + margin: 24px 0; + padding: 16px; + border: 1px solid var(--border); + border-radius: var(--radius-md, 20px); + background: rgba(255,255,255,.03); + overflow-x: auto; +} + +.mermaid-diagram { + display: flex; + justify-content: center; +} + +.mermaid-diagram > svg { + max-width: 100%; + height: auto; +} + +.mermaid-caption { + margin-top: 10px; + font-size: 13px; + line-height: 1.55; + color: var(--text-muted); + text-align: center; +} + +.mermaid-loading { + position: relative; + width: 100%; + color: var(--text-soft); + font-size: 13px; +} +.mermaid-loading::after { + content: "Loading diagram\2026"; +} + +@media (prefers-reduced-motion: reduce) { + .mermaid-loading::after { content: "Loading diagram"; } +} + + +/* TOKEN PROPOSALS */ +/* (none). The existing --primary, --primary-soft, --primary-border, + --info, --success, --warning, --error, --text-muted, --border, --radius-* + tokens cover every selector above. If we later want named aside tints, + propose: + --aside-tint-info: rgba(147,197,253,.045); + --aside-tint-success: rgba(99,230,190,.045); + --aside-tint-warning: rgba(255,209,102,.045); + --aside-tint-error: rgba(255,107,107,.05); +*/ + + +/* === islands === */ +/* + * Scope: Algolia DocSearch (.DocSearch-*) and AskAI menu (.ask-ai*). + * + * Notes: + * - components.css currently has NO rules in either namespace, so every block + * below is NEW. No REPLACES/APPEND-TO entries. + * - We scope DocSearch overrides under .suites-docsearch where possible so + * they cannot leak. The full-screen modal (.DocSearch-Container) is portaled + * to <body>, so its theming uses --docsearch-* CSS vars set at :root level + * (Algolia's documented theming API; works regardless of portal target). + * - Brand pink #EF4779 surfaces on hit highlights, primary action, kbd hints. + * - Respects prefers-reduced-motion: all transitions disabled. + */ + + +/* ============================================================ + * DocSearch theme variables (Algolia's documented API) + * Setting these on :root themes the portaled modal too. + * ============================================================ */ + +/* NEW */ +:root { + /* Type */ + --docsearch-primary-color: var(--primary, #EF4779); + --docsearch-text-color: var(--text, #F4F4F5); + --docsearch-muted-color: var(--text-muted, #A1A1AA); + + /* Surfaces */ + --docsearch-container-background: rgba(8, 8, 11, 0.78); + --docsearch-modal-background: var(--surface, #17171C); + --docsearch-searchbox-background: var(--bg-subtle, #131318); + --docsearch-searchbox-focus-background: var(--bg, #0F0F12); + --docsearch-hit-background: var(--surface-raised, #202027); + --docsearch-hit-color: var(--text, #F4F4F5); + --docsearch-hit-active-color: #ffffff; + --docsearch-hit-shadow: none; + --docsearch-footer-background: var(--bg-subtle, #131318); + --docsearch-footer-shadow: inset 0 1px 0 0 var(--border, rgba(255, 255, 255, 0.09)); + + /* Key hints (cmd/ctrl/k chips) */ + --docsearch-key-gradient: linear-gradient(180deg, #2a2a33 0%, #1c1c22 100%); + --docsearch-key-shadow: + inset 0 -2px 0 0 rgba(0, 0, 0, 0.4), + inset 0 0 1px 1px rgba(255, 255, 255, 0.06), + 0 1px 2px rgba(0, 0, 0, 0.3); + --docsearch-key-pressed-shadow: + inset 0 -1px 0 0 rgba(0, 0, 0, 0.5), + inset 0 0 1px 1px rgba(255, 255, 255, 0.04), + 0 1px 1px rgba(0, 0, 0, 0.3); + + /* Modal sizing + radius */ + --docsearch-modal-shadow: var(--shadow, 0 24px 90px rgba(0, 0, 0, 0.45)); + --docsearch-spacing: 12px; + --docsearch-icon-stroke-width: 1.6; + --docsearch-highlight-color: var(--primary, #EF4779); + --docsearch-logo-color: var(--text-muted, #A1A1AA); +} + + +/* ============================================================ + * DocSearch trigger button (the "/Search documentation" chip in the header) + * + * We do NOT style the wrapper .nav-search; that belongs to site-chrome. + * Below we only refine the internal DocSearch button so brand colors apply + * to the icon + kbd hints. + * ============================================================ */ + +/* NEW */ +.suites-docsearch .DocSearch-Button { + background: var(--bg-subtle, #131318); + border: 1px solid var(--border, rgba(255, 255, 255, 0.09)); + color: var(--text-muted, #A1A1AA); + border-radius: var(--radius-sm, 14px); + padding: 0 10px; + height: 36px; + transition: border-color 180ms ease, background-color 180ms ease, color 180ms ease; +} + +/* NEW */ +.suites-docsearch .DocSearch-Button:hover { + background: var(--surface, #17171C); + border-color: var(--border-strong, rgba(255, 255, 255, 0.16)); + color: var(--text, #F4F4F5); + box-shadow: none; +} + +/* NEW */ +.suites-docsearch .DocSearch-Button:focus-visible { + outline: 2px solid var(--primary, #EF4779); + outline-offset: 2px; + border-color: var(--primary, #EF4779); +} + +/* NEW */ +.suites-docsearch .DocSearch-Button-Placeholder { + font-size: 14px; + font-family: var(--font-body); + padding: 0 8px 0 4px; +} + +/* NEW */ +.suites-docsearch .DocSearch-Search-Icon { + width: 16px; + height: 16px; + color: var(--text-soft, #8B8B94); +} + +/* NEW */ +.suites-docsearch .DocSearch-Button-Keys { + min-width: auto; + gap: 2px; +} + +/* NEW */ +.suites-docsearch .DocSearch-Button-Key { + background: var(--surface-raised, #202027); + box-shadow: var(--docsearch-key-shadow); + color: var(--text, #F4F4F5); + font-family: var(--font-code); + font-size: 11px; + padding: 0 5px; + height: 20px; + min-width: 20px; + border-radius: 6px; +} + + +/* ============================================================ + * DocSearch modal (portaled to <body>, so styled at top level) + * ============================================================ */ + +/* NEW */ +.DocSearch-Container { + /* Slightly stronger backdrop blur for legibility on busy docs pages */ + backdrop-filter: blur(8px) saturate(120%); + -webkit-backdrop-filter: blur(8px) saturate(120%); + z-index: var(--z-modal, 50); +} + +/* NEW */ +.DocSearch-Modal { + background: var(--surface, #17171C); + border: 1px solid var(--border, rgba(255, 255, 255, 0.09)); + border-radius: var(--radius-md, 20px); + box-shadow: var(--shadow, 0 24px 90px rgba(0, 0, 0, 0.45)); + font-family: var(--font-body); +} + +/* NEW */ +.DocSearch-SearchBar { + padding: 12px 14px 0; +} + +/* NEW */ +.DocSearch-Form { + background: var(--bg-subtle, #131318); + border: 1px solid var(--border, rgba(255, 255, 255, 0.09)); + border-radius: var(--radius-sm, 14px); + box-shadow: none; + height: 48px; +} + +/* NEW */ +.DocSearch-Form:focus-within { + border-color: var(--primary, #EF4779); + box-shadow: 0 0 0 3px rgba(239, 71, 121, 0.18); +} + +/* NEW */ +.DocSearch-Input { + color: var(--text, #F4F4F5); + font-size: 15px; + font-family: var(--font-body); +} + +/* NEW */ +.DocSearch-Input::placeholder { + color: var(--text-soft, #8B8B94); + opacity: 1; +} + +/* NEW */ +.DocSearch-Input:focus { + outline: none; +} + +/* NEW */ +.DocSearch-MagnifierLabel, +.DocSearch-Reset { + color: var(--text-soft, #8B8B94); +} + +/* NEW */ +.DocSearch-Cancel { + color: var(--primary, #EF4779); + font-weight: 500; +} + +/* NEW: keyboard focus on the close button */ +.DocSearch-Cancel:focus-visible { + outline: 2px solid var(--primary, #EF4779); + outline-offset: 2px; + border-radius: 4px; +} + + +/* ============================================================ + * Hits list + * ============================================================ */ + +/* NEW */ +.DocSearch-Hits { + padding: 4px 14px 14px; +} + +/* NEW */ +.DocSearch-Hit-source { + color: var(--text-muted, #A1A1AA); + font-size: 12px; + letter-spacing: 0.04em; + text-transform: uppercase; + background: transparent; + margin: 12px 0 6px; +} + +/* NEW */ +.DocSearch-Hit { + padding-bottom: 4px; +} + +/* NEW */ +.DocSearch-Hit a { + background: var(--surface-raised, #202027); + border: 1px solid transparent; + border-radius: var(--radius-xs, 10px); + padding: 10px 12px; + transition: background-color 180ms ease, border-color 180ms ease, transform 180ms ease; + box-shadow: none; +} + +/* NEW: active (hover/keyboard-selected) hit */ +.DocSearch-Hit[aria-selected="true"] a, +.DocSearch-Hit a:hover { + background: var(--primary, #EF4779); + border-color: var(--primary, #EF4779); + color: #ffffff; +} + +/* NEW: ensure all text inside an active hit goes white (Algolia paints */ +/* secondary text muted by default) */ +.DocSearch-Hit[aria-selected="true"] a *, +.DocSearch-Hit a:hover * { + color: #ffffff !important; +} + +/* NEW: highlighted matched substring inside a hit title */ +.DocSearch-Hit mark, +.DocSearch-Hit-Tree { + background: transparent; + color: var(--primary-light, #FF9EBB); + font-weight: 600; +} + +/* NEW: matched substring inside an active hit (selected/hover) */ +.DocSearch-Hit[aria-selected="true"] mark, +.DocSearch-Hit a:hover mark { + color: #ffffff; + text-decoration: underline; + text-underline-offset: 2px; +} + +/* NEW */ +.DocSearch-Hit-icon, +.DocSearch-Hit-action { + color: var(--text-soft, #8B8B94); +} + +/* NEW */ +.DocSearch-Hit[aria-selected="true"] .DocSearch-Hit-icon, +.DocSearch-Hit[aria-selected="true"] .DocSearch-Hit-action { + color: rgba(255, 255, 255, 0.9); +} + + +/* ============================================================ + * Start screen (recent + favorites) + * ============================================================ */ + +/* NEW */ +.DocSearch-StartScreen, +.DocSearch-NoResults { + padding: 18px 14px 8px; +} + +/* NEW */ +.DocSearch-Help { + color: var(--text-muted, #A1A1AA); + font-size: 13px; +} + +/* NEW */ +.DocSearch-Title { + color: var(--text, #F4F4F5); + font-family: var(--font-title); +} + +/* NEW */ +.DocSearch-NoResults-Prefill-List { + padding-top: 6px; +} + + +/* ============================================================ + * Footer (keyboard hint row) + * ============================================================ */ + +/* NEW */ +.DocSearch-Footer { + border-radius: 0 0 var(--radius-md, 20px) var(--radius-md, 20px); + padding: 8px 14px; +} + +/* NEW */ +.DocSearch-Commands-Key { + background: var(--surface-raised, #202027); + box-shadow: var(--docsearch-key-shadow); + color: var(--text-muted, #A1A1AA); + border-radius: 6px; +} + +/* NEW */ +.DocSearch-Label { + color: var(--text-soft, #8B8B94); + font-size: 11px; +} + +/* NEW */ +.DocSearch-Logo a { + color: var(--text-soft, #8B8B94); + opacity: 0.7; + transition: opacity 180ms ease; +} + +/* NEW */ +.DocSearch-Logo a:hover { + opacity: 1; +} + + +/* ============================================================ + * Reduced motion: kill all transitions inside the modal + trigger + * ============================================================ */ + +/* NEW */ +@media (prefers-reduced-motion: reduce) { + .suites-docsearch .DocSearch-Button, + .DocSearch-Hit a, + .DocSearch-Form, + .DocSearch-Logo a { + transition: none !important; + } +} + + +/* ============================================================ + * AskAI menu (.ask-ai*) - popover with two provider choices + * ============================================================ */ + +/* NEW */ +.ask-ai-root { + position: relative; + display: inline-flex; +} + +/* NEW: the menu container */ +.ask-ai-menu { + position: absolute; + top: calc(100% + 6px); + right: 0; + min-width: 180px; + background: var(--surface, #17171C); + border: 1px solid var(--border, rgba(255, 255, 255, 0.09)); + border-radius: var(--radius-sm, 14px); + box-shadow: var(--shadow, 0 24px 90px rgba(0, 0, 0, 0.45)); + padding: 4px; + display: flex; + flex-direction: column; + gap: 2px; + z-index: var(--z-overlay, 40); + animation: ask-ai-menu-in 140ms ease-out; + transform-origin: top right; +} + +/* NEW: floating variant pins to the screen-anchored trigger */ +.ask-ai-menu[data-variant="floating"] { + right: 0; +} + +/* NEW */ +@keyframes ask-ai-menu-in { + from { + opacity: 0; + transform: translateY(-4px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +/* NEW: individual menu item */ +.ask-ai-menu-item { + display: inline-flex; + align-items: center; + gap: 10px; + width: 100%; + text-align: left; + background: transparent; + border: 1px solid transparent; + color: var(--text, #F4F4F5); + font-family: var(--font-body); + font-size: 14px; + font-weight: 500; + padding: 8px 10px; + border-radius: 10px; + cursor: pointer; + min-height: 36px; + transition: background-color 160ms ease, color 160ms ease, border-color 160ms ease; +} + +/* NEW: icons inside menu items inherit a softer tint by default */ +.ask-ai-menu-item > svg { + color: var(--text-muted, #A1A1AA); + width: 16px; + height: 16px; + flex-shrink: 0; + transition: color 160ms ease; +} + +/* NEW: hover + keyboard-focus state - brand pink wash */ +.ask-ai-menu-item:hover, +.ask-ai-menu-item:focus-visible { + background: var(--primary-soft, rgba(239, 71, 121, 0.12)); + color: var(--text, #F4F4F5); + outline: none; + border-color: var(--primary-border, rgba(239, 71, 121, 0.32)); +} + +/* NEW: focus-visible also gets a clear ring for keyboard users */ +.ask-ai-menu-item:focus-visible { + box-shadow: 0 0 0 2px rgba(239, 71, 121, 0.35); +} + +/* NEW: focused/hover icon picks up brand pink */ +.ask-ai-menu-item:hover > svg, +.ask-ai-menu-item:focus-visible > svg { + color: var(--primary, #EF4779); +} + +/* NEW: keep touch targets >= 44px on coarse pointers */ +@media (pointer: coarse) { + .ask-ai-menu-item { + min-height: 44px; + padding: 10px 12px; + } +} + +/* NEW */ +@media (prefers-reduced-motion: reduce) { + .ask-ai-menu { + animation: none; + } + .ask-ai-menu-item, + .ask-ai-menu-item > svg { + transition: none; + } +} + + +/* ============================================================ + * TOKEN PROPOSALS (informational; not applied here) + * ============================================================ + * + * If tokens.css gains the following, the rules above would simplify: + * + * --backdrop: rgba(8, 8, 11, 0.78); // matches docsearch container + * --ring-primary: 0 0 0 3px rgba(239, 71, 121, 0.18); + * --kbd-bg: linear-gradient(180deg, #2a2a33 0%, #1c1c22 100%); + * --kbd-shadow: inset 0 -2px 0 0 rgba(0,0,0,.4), + * inset 0 0 1px 1px rgba(255,255,255,.06), + * 0 1px 2px rgba(0,0,0,.3); + * + * No changes were made to tokens.css per scope constraints. + */ diff --git a/.scratch/ui-ux/docs-chrome.css b/.scratch/ui-ux/docs-chrome.css new file mode 100644 index 0000000..b4b3f63 --- /dev/null +++ b/.scratch/ui-ux/docs-chrome.css @@ -0,0 +1,195 @@ +/* === docs-chrome === */ +/* Audit and fixes for sidebar, TOC, and prev/next pager. + Pairs with edits to: + src/components/docs/Sidebar.astro (wraps groups in <nav>, items in <ul>/<li>) + src/components/docs/TOC.astro (adds role="list", aria-current="location") + src/components/docs/PrevNext.astro (drops .btn, adds eyebrow + label spans) */ + +/* ---------- Sidebar ---------------------------------------------------- */ + +/* NEW: outer landmark wrapper now rendered by Sidebar.astro. + Keep zero chrome so layout stays identical to the old bare-details list. */ +.sidebar-nav { + display: block; +} + +/* APPEND-TO: .sidebar a (components.css L790) + Slightly tighter focus offset so the global 2px ring tucks against the link + instead of bleeding into the adjacent structure-line. */ +.sidebar a:focus-visible { + outline-offset: -2px; +} + +/* REPLACES: components.css L1249-L1258 + .sidebar-group-items was a <div>; it is now a <ul role="list">. Reset list + chrome (padding/margin/list-style) and keep the existing flex layout and + structure-line. li children get zero margin so spacing comes from the + gap on the flex parent. */ +.sidebar-group-items { + list-style: none; + display: flex; + flex-direction: column; + gap: 2px; + padding: 4px 0 0; + margin: 0 0 0 14px; + padding-left: 12px; + border-left: 1px solid var(--border); + animation: sidebar-group-fade-in 180ms ease-out; +} + +/* NEW: li children inside the new <ul> wrapper. */ +.sidebar-group-items > li { + margin: 0; + padding: 0; + min-width: 0; +} + +/* APPEND-TO: .sidebar a (components.css L790) + Long labels (e.g. "Install Suites for NestJS, Inversify, Jest, Vitest...") + must wrap inside the 268px sidebar instead of pushing the column wider. */ +.sidebar a { + min-width: 0; + overflow-wrap: anywhere; +} + +/* ---------- TOC -------------------------------------------------------- */ + +/* APPEND-TO: .toc-list a.active (components.css L961) + Mirror the .active class style for aria-current="location" so the script's + ARIA update alone is enough if the class is ever dropped. */ +.toc-list a[aria-current="location"] { + color: var(--primary-light); + border-left-color: var(--primary); + font-weight: 600; +} + +/* APPEND-TO: .toc-list a:focus-visible (components.css L971) + Pull the global 2px ring inward so it does not visually merge with the + 2px left-border bar used for the active indicator. */ +.toc-list a:focus-visible { + outline-offset: -2px; +} + +/* ---------- Prev / Next pager ----------------------------------------- */ + +/* REPLACES: components.css L1277-L1285 + Add min-width:0 propagation so flex children can shrink under long labels + without overflowing the article column. */ +.doc-pager { + display: flex; + justify-content: space-between; + align-items: stretch; + gap: 16px; + margin-top: 48px; + padding-top: 24px; + border-top: 1px solid var(--border); + min-width: 0; +} + +/* REPLACES: components.css L1287-L1299 + The pager anchors no longer inherit .btn (pill, 42px min-height). Restate + the full box here so the cards keep their card-style border/background and + gain min-width:0 + overflow-wrap so long titles wrap to two lines. */ +.doc-pager a { + flex: 1 1 0; + min-width: 0; + display: flex; + flex-direction: column; + gap: 4px; + padding: 14px 18px; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--text-muted); + background: rgba(255,255,255,.02); + text-decoration: none; + transition: border-color 200ms ease-out, color 200ms ease-out, background 200ms ease-out; +} + +/* APPEND-TO: .doc-pager a:hover (components.css L1301) */ +.doc-pager a:hover { + border-color: var(--primary-border); +} + +/* REPLACES: components.css L1307-L1313 + The first child is now .doc-pager-eyebrow (a span containing the arrow + plus the word "Previous"/"Next"). Style the eyebrow itself, not whatever + span happened to be first. */ +.doc-pager-eyebrow { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 12px; + font-weight: 600; + color: var(--text-soft); + text-transform: uppercase; + letter-spacing: 0.06em; + line-height: 1; +} + +/* REPLACES: components.css L1315-L1319 + The label is now .doc-pager-label. Allow it to wrap to two lines on + narrow viewports without forcing a column-stretch. */ +.doc-pager-label { + display: block; + font-size: 15px; + font-weight: 700; + color: var(--text); + line-height: 1.35; + overflow-wrap: anywhere; + /* Clamp very long titles to two lines on desktop. Falls back to natural + wrap where line-clamp isn't supported. */ + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* NEW: arrow glyph inside the eyebrow. */ +.doc-pager-arrow { + font-size: 14px; + line-height: 1; + color: var(--text-muted); + transition: transform 180ms ease-out, color 180ms ease-out; +} + +.doc-pager a:hover .doc-pager-arrow { + color: var(--primary-light); +} + +.doc-pager-prev:hover .doc-pager-arrow { transform: translateX(-2px); } +.doc-pager-next:hover .doc-pager-arrow { transform: translateX(2px); } + +@media (prefers-reduced-motion: reduce) { + .doc-pager-arrow { transition: none; } + .doc-pager-prev:hover .doc-pager-arrow, + .doc-pager-next:hover .doc-pager-arrow { transform: none; } +} + +/* NEW: empty side placeholder keeps the grid balanced when only prev or + only next exists. Mirrors a card's outer footprint but stays invisible. */ +.doc-pager-spacer { + flex: 1 1 0; + min-width: 0; +} + +/* APPEND-TO: .doc-pager-prev / .doc-pager-next (components.css L1321-L1322) + Re-state alignment now that .btn is no longer applied. */ +.doc-pager-prev { text-align: left; align-items: flex-start; } +.doc-pager-next { text-align: right; align-items: flex-end; } + +/* Focus ring sits inside the rounded border. */ +.doc-pager a:focus-visible { + outline-offset: -2px; + border-radius: var(--radius-sm); +} + +/* APPEND-TO: @media (max-width: 640px) .doc-pager (components.css L1324) + Touch-target floor: pager cards stack on small screens. Ensure each card + clears the 44x44 minimum without inflating desktop padding. */ +@media (max-width: 640px) { + .doc-pager a { min-height: 44px; } + .doc-pager-label { -webkit-line-clamp: 3; } +} + +/* TOKEN PROPOSALS */ +/* None. All values reuse existing tokens in tokens.css. */ diff --git a/.scratch/ui-ux/islands.css b/.scratch/ui-ux/islands.css new file mode 100644 index 0000000..da60582 --- /dev/null +++ b/.scratch/ui-ux/islands.css @@ -0,0 +1,495 @@ +/* === islands === */ +/* + * Scope: Algolia DocSearch (.DocSearch-*) and AskAI menu (.ask-ai*). + * + * Notes: + * - components.css currently has NO rules in either namespace, so every block + * below is NEW. No REPLACES/APPEND-TO entries. + * - We scope DocSearch overrides under .suites-docsearch where possible so + * they cannot leak. The full-screen modal (.DocSearch-Container) is portaled + * to <body>, so its theming uses --docsearch-* CSS vars set at :root level + * (Algolia's documented theming API; works regardless of portal target). + * - Brand pink #EF4779 surfaces on hit highlights, primary action, kbd hints. + * - Respects prefers-reduced-motion: all transitions disabled. + */ + + +/* ============================================================ + * DocSearch theme variables (Algolia's documented API) + * Setting these on :root themes the portaled modal too. + * ============================================================ */ + +/* NEW */ +:root { + /* Type */ + --docsearch-primary-color: var(--primary, #EF4779); + --docsearch-text-color: var(--text, #F4F4F5); + --docsearch-muted-color: var(--text-muted, #A1A1AA); + + /* Surfaces */ + --docsearch-container-background: rgba(8, 8, 11, 0.78); + --docsearch-modal-background: var(--surface, #17171C); + --docsearch-searchbox-background: var(--bg-subtle, #131318); + --docsearch-searchbox-focus-background: var(--bg, #0F0F12); + --docsearch-hit-background: var(--surface-raised, #202027); + --docsearch-hit-color: var(--text, #F4F4F5); + --docsearch-hit-active-color: #ffffff; + --docsearch-hit-shadow: none; + --docsearch-footer-background: var(--bg-subtle, #131318); + --docsearch-footer-shadow: inset 0 1px 0 0 var(--border, rgba(255, 255, 255, 0.09)); + + /* Key hints (cmd/ctrl/k chips) */ + --docsearch-key-gradient: linear-gradient(180deg, #2a2a33 0%, #1c1c22 100%); + --docsearch-key-shadow: + inset 0 -2px 0 0 rgba(0, 0, 0, 0.4), + inset 0 0 1px 1px rgba(255, 255, 255, 0.06), + 0 1px 2px rgba(0, 0, 0, 0.3); + --docsearch-key-pressed-shadow: + inset 0 -1px 0 0 rgba(0, 0, 0, 0.5), + inset 0 0 1px 1px rgba(255, 255, 255, 0.04), + 0 1px 1px rgba(0, 0, 0, 0.3); + + /* Modal sizing + radius */ + --docsearch-modal-shadow: var(--shadow, 0 24px 90px rgba(0, 0, 0, 0.45)); + --docsearch-spacing: 12px; + --docsearch-icon-stroke-width: 1.6; + --docsearch-highlight-color: var(--primary, #EF4779); + --docsearch-logo-color: var(--text-muted, #A1A1AA); +} + + +/* ============================================================ + * DocSearch trigger button (the "/Search documentation" chip in the header) + * + * We do NOT style the wrapper .nav-search; that belongs to site-chrome. + * Below we only refine the internal DocSearch button so brand colors apply + * to the icon + kbd hints. + * ============================================================ */ + +/* NEW */ +.suites-docsearch .DocSearch-Button { + background: var(--bg-subtle, #131318); + border: 1px solid var(--border, rgba(255, 255, 255, 0.09)); + color: var(--text-muted, #A1A1AA); + border-radius: var(--radius-sm, 14px); + padding: 0 10px; + height: 36px; + transition: border-color 180ms ease, background-color 180ms ease, color 180ms ease; +} + +/* NEW */ +.suites-docsearch .DocSearch-Button:hover { + background: var(--surface, #17171C); + border-color: var(--border-strong, rgba(255, 255, 255, 0.16)); + color: var(--text, #F4F4F5); + box-shadow: none; +} + +/* NEW */ +.suites-docsearch .DocSearch-Button:focus-visible { + outline: 2px solid var(--primary, #EF4779); + outline-offset: 2px; + border-color: var(--primary, #EF4779); +} + +/* NEW */ +.suites-docsearch .DocSearch-Button-Placeholder { + font-size: 14px; + font-family: var(--font-body); + padding: 0 8px 0 4px; +} + +/* NEW */ +.suites-docsearch .DocSearch-Search-Icon { + width: 16px; + height: 16px; + color: var(--text-soft, #8B8B94); +} + +/* NEW */ +.suites-docsearch .DocSearch-Button-Keys { + min-width: auto; + gap: 2px; +} + +/* NEW */ +.suites-docsearch .DocSearch-Button-Key { + background: var(--surface-raised, #202027); + box-shadow: var(--docsearch-key-shadow); + color: var(--text, #F4F4F5); + font-family: var(--font-code); + font-size: 11px; + padding: 0 5px; + height: 20px; + min-width: 20px; + border-radius: 6px; +} + + +/* ============================================================ + * DocSearch modal (portaled to <body>, so styled at top level) + * ============================================================ */ + +/* NEW */ +.DocSearch-Container { + /* Slightly stronger backdrop blur for legibility on busy docs pages */ + backdrop-filter: blur(8px) saturate(120%); + -webkit-backdrop-filter: blur(8px) saturate(120%); + z-index: var(--z-modal, 50); +} + +/* NEW */ +.DocSearch-Modal { + background: var(--surface, #17171C); + border: 1px solid var(--border, rgba(255, 255, 255, 0.09)); + border-radius: var(--radius-md, 20px); + box-shadow: var(--shadow, 0 24px 90px rgba(0, 0, 0, 0.45)); + font-family: var(--font-body); +} + +/* NEW */ +.DocSearch-SearchBar { + padding: 12px 14px 0; +} + +/* NEW */ +.DocSearch-Form { + background: var(--bg-subtle, #131318); + border: 1px solid var(--border, rgba(255, 255, 255, 0.09)); + border-radius: var(--radius-sm, 14px); + box-shadow: none; + height: 48px; +} + +/* NEW */ +.DocSearch-Form:focus-within { + border-color: var(--primary, #EF4779); + box-shadow: 0 0 0 3px rgba(239, 71, 121, 0.18); +} + +/* NEW */ +.DocSearch-Input { + color: var(--text, #F4F4F5); + font-size: 15px; + font-family: var(--font-body); +} + +/* NEW */ +.DocSearch-Input::placeholder { + color: var(--text-soft, #8B8B94); + opacity: 1; +} + +/* NEW */ +.DocSearch-Input:focus { + outline: none; +} + +/* NEW */ +.DocSearch-MagnifierLabel, +.DocSearch-Reset { + color: var(--text-soft, #8B8B94); +} + +/* NEW */ +.DocSearch-Cancel { + color: var(--primary, #EF4779); + font-weight: 500; +} + +/* NEW: keyboard focus on the close button */ +.DocSearch-Cancel:focus-visible { + outline: 2px solid var(--primary, #EF4779); + outline-offset: 2px; + border-radius: 4px; +} + + +/* ============================================================ + * Hits list + * ============================================================ */ + +/* NEW */ +.DocSearch-Hits { + padding: 4px 14px 14px; +} + +/* NEW */ +.DocSearch-Hit-source { + color: var(--text-muted, #A1A1AA); + font-size: 12px; + letter-spacing: 0.04em; + text-transform: uppercase; + background: transparent; + margin: 12px 0 6px; +} + +/* NEW */ +.DocSearch-Hit { + padding-bottom: 4px; +} + +/* NEW */ +.DocSearch-Hit a { + background: var(--surface-raised, #202027); + border: 1px solid transparent; + border-radius: var(--radius-xs, 10px); + padding: 10px 12px; + transition: background-color 180ms ease, border-color 180ms ease, transform 180ms ease; + box-shadow: none; +} + +/* NEW: active (hover/keyboard-selected) hit */ +.DocSearch-Hit[aria-selected="true"] a, +.DocSearch-Hit a:hover { + background: var(--primary, #EF4779); + border-color: var(--primary, #EF4779); + color: #ffffff; +} + +/* NEW: ensure all text inside an active hit goes white (Algolia paints */ +/* secondary text muted by default) */ +.DocSearch-Hit[aria-selected="true"] a *, +.DocSearch-Hit a:hover * { + color: #ffffff !important; +} + +/* NEW: highlighted matched substring inside a hit title */ +.DocSearch-Hit mark, +.DocSearch-Hit-Tree { + background: transparent; + color: var(--primary-light, #FF9EBB); + font-weight: 600; +} + +/* NEW: matched substring inside an active hit (selected/hover) */ +.DocSearch-Hit[aria-selected="true"] mark, +.DocSearch-Hit a:hover mark { + color: #ffffff; + text-decoration: underline; + text-underline-offset: 2px; +} + +/* NEW */ +.DocSearch-Hit-icon, +.DocSearch-Hit-action { + color: var(--text-soft, #8B8B94); +} + +/* NEW */ +.DocSearch-Hit[aria-selected="true"] .DocSearch-Hit-icon, +.DocSearch-Hit[aria-selected="true"] .DocSearch-Hit-action { + color: rgba(255, 255, 255, 0.9); +} + + +/* ============================================================ + * Start screen (recent + favorites) + * ============================================================ */ + +/* NEW */ +.DocSearch-StartScreen, +.DocSearch-NoResults { + padding: 18px 14px 8px; +} + +/* NEW */ +.DocSearch-Help { + color: var(--text-muted, #A1A1AA); + font-size: 13px; +} + +/* NEW */ +.DocSearch-Title { + color: var(--text, #F4F4F5); + font-family: var(--font-title); +} + +/* NEW */ +.DocSearch-NoResults-Prefill-List { + padding-top: 6px; +} + + +/* ============================================================ + * Footer (keyboard hint row) + * ============================================================ */ + +/* NEW */ +.DocSearch-Footer { + border-radius: 0 0 var(--radius-md, 20px) var(--radius-md, 20px); + padding: 8px 14px; +} + +/* NEW */ +.DocSearch-Commands-Key { + background: var(--surface-raised, #202027); + box-shadow: var(--docsearch-key-shadow); + color: var(--text-muted, #A1A1AA); + border-radius: 6px; +} + +/* NEW */ +.DocSearch-Label { + color: var(--text-soft, #8B8B94); + font-size: 11px; +} + +/* NEW */ +.DocSearch-Logo a { + color: var(--text-soft, #8B8B94); + opacity: 0.7; + transition: opacity 180ms ease; +} + +/* NEW */ +.DocSearch-Logo a:hover { + opacity: 1; +} + + +/* ============================================================ + * Reduced motion: kill all transitions inside the modal + trigger + * ============================================================ */ + +/* NEW */ +@media (prefers-reduced-motion: reduce) { + .suites-docsearch .DocSearch-Button, + .DocSearch-Hit a, + .DocSearch-Form, + .DocSearch-Logo a { + transition: none !important; + } +} + + +/* ============================================================ + * AskAI menu (.ask-ai*) - popover with two provider choices + * ============================================================ */ + +/* NEW */ +.ask-ai-root { + position: relative; + display: inline-flex; +} + +/* NEW: the menu container */ +.ask-ai-menu { + position: absolute; + top: calc(100% + 6px); + right: 0; + min-width: 180px; + background: var(--surface, #17171C); + border: 1px solid var(--border, rgba(255, 255, 255, 0.09)); + border-radius: var(--radius-sm, 14px); + box-shadow: var(--shadow, 0 24px 90px rgba(0, 0, 0, 0.45)); + padding: 4px; + display: flex; + flex-direction: column; + gap: 2px; + z-index: var(--z-overlay, 40); + animation: ask-ai-menu-in 140ms ease-out; + transform-origin: top right; +} + +/* NEW: floating variant pins to the screen-anchored trigger */ +.ask-ai-menu[data-variant="floating"] { + right: 0; +} + +/* NEW */ +@keyframes ask-ai-menu-in { + from { + opacity: 0; + transform: translateY(-4px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +/* NEW: individual menu item */ +.ask-ai-menu-item { + display: inline-flex; + align-items: center; + gap: 10px; + width: 100%; + text-align: left; + background: transparent; + border: 1px solid transparent; + color: var(--text, #F4F4F5); + font-family: var(--font-body); + font-size: 14px; + font-weight: 500; + padding: 8px 10px; + border-radius: 10px; + cursor: pointer; + min-height: 36px; + transition: background-color 160ms ease, color 160ms ease, border-color 160ms ease; +} + +/* NEW: icons inside menu items inherit a softer tint by default */ +.ask-ai-menu-item > svg { + color: var(--text-muted, #A1A1AA); + width: 16px; + height: 16px; + flex-shrink: 0; + transition: color 160ms ease; +} + +/* NEW: hover + keyboard-focus state - brand pink wash */ +.ask-ai-menu-item:hover, +.ask-ai-menu-item:focus-visible { + background: var(--primary-soft, rgba(239, 71, 121, 0.12)); + color: var(--text, #F4F4F5); + outline: none; + border-color: var(--primary-border, rgba(239, 71, 121, 0.32)); +} + +/* NEW: focus-visible also gets a clear ring for keyboard users */ +.ask-ai-menu-item:focus-visible { + box-shadow: 0 0 0 2px rgba(239, 71, 121, 0.35); +} + +/* NEW: focused/hover icon picks up brand pink */ +.ask-ai-menu-item:hover > svg, +.ask-ai-menu-item:focus-visible > svg { + color: var(--primary, #EF4779); +} + +/* NEW: keep touch targets >= 44px on coarse pointers */ +@media (pointer: coarse) { + .ask-ai-menu-item { + min-height: 44px; + padding: 10px 12px; + } +} + +/* NEW */ +@media (prefers-reduced-motion: reduce) { + .ask-ai-menu { + animation: none; + } + .ask-ai-menu-item, + .ask-ai-menu-item > svg { + transition: none; + } +} + + +/* ============================================================ + * TOKEN PROPOSALS (informational; not applied here) + * ============================================================ + * + * If tokens.css gains the following, the rules above would simplify: + * + * --backdrop: rgba(8, 8, 11, 0.78); // matches docsearch container + * --ring-primary: 0 0 0 3px rgba(239, 71, 121, 0.18); + * --kbd-bg: linear-gradient(180deg, #2a2a33 0%, #1c1c22 100%); + * --kbd-shadow: inset 0 -2px 0 0 rgba(0,0,0,.4), + * inset 0 0 1px 1px rgba(255,255,255,.06), + * 0 1px 2px rgba(0,0,0,.3); + * + * No changes were made to tokens.css per scope constraints. + */ diff --git a/.scratch/ui-ux/mdx.css b/.scratch/ui-ux/mdx.css new file mode 100644 index 0000000..0fed9d8 --- /dev/null +++ b/.scratch/ui-ux/mdx.css @@ -0,0 +1,153 @@ +/* === mdx === */ +/* Surgical deltas for src/components/mdx/* (Aside, Tabs, TabItem, MermaidDiagram). + All selectors here are inside the documented scope: .aside*, .tabs, .tab, + .tab.active. Mermaid additions use a fresh .mermaid-* namespace introduced + by the component itself. */ + + +/* ---------- Aside ---------------------------------------------------------- */ + +/* APPEND-TO: .aside (components.css L541-L549) + Tighten left-rule paint when role="note" is announced (no change in look) + and add a subtle variant tint so the four severity colors are visually + distinct on the body, not only on the icon tile. Tint alphas chosen so + --text-muted (#A1A1AA, 8.4:1 on #0F0F12) keeps >= 4.5:1 contrast on top. */ +.aside { isolation: isolate; } + +/* APPEND-TO: .aside-info / .aside-success / .aside-warning / .aside-error + (components.css L616-L619) + Add a faint tinted wash to the card surface. The left-border accent and + icon tile colors are already set; this only changes the background. */ +.aside-info { background: linear-gradient(to right, rgba(147,197,253,.045), rgba(255,255,255,.03) 40%); } +.aside-success { background: linear-gradient(to right, rgba(99,230,190,.045), rgba(255,255,255,.03) 40%); } +.aside-warning { background: linear-gradient(to right, rgba(255,209,102,.045), rgba(255,255,255,.03) 40%); } +.aside-error { background: linear-gradient(to right, rgba(255,107,107,.05), rgba(255,255,255,.03) 40%); } + +/* APPEND-TO: .aside-title (components.css L572-L580) + <strong> is bold by default; reset and rely on the token weight. Keep the + element semantically a strong-emphasis label rather than a heading so the + article outline is not polluted by every Aside title. */ +.aside-title { font-style: normal; font-weight: 720; } + + +/* ---------- Tabs ----------------------------------------------------------- */ + +/* REPLACES: components.css L655-L662 (.tabs) + Add flex-wrap so the rail doesn't horizontally overflow the 72ch article + column on narrow viewports, and a row-gap so wrapped pills line up cleanly. */ +.tabs { + display: inline-flex; + flex-wrap: wrap; + row-gap: 3px; + padding: 4px; + border: 1px solid var(--border); + border-radius: 999px; + background: rgba(255,255,255,.03); + gap: 3px; + max-width: 100%; +} + +/* REPLACES: components.css L664-L670 (.tab) + Add background:transparent (so inactive tabs paint over the rail), color + transition for smooth selection, and an explicit border:0 reset since + buttons inherit UA borders. Cursor and disabled state are inherited from + the existing .btn, .tab, [role="button"] block at L1119-L1120. */ +.tab { + border: 0; + background: transparent; + border-radius: 999px; + color: var(--text-muted); + font-size: 13px; + font-weight: 700; + padding: 8px 13px; + transition: color 180ms ease, background-color 180ms ease, box-shadow 180ms ease; +} + +/* NEW: hover affordance for inactive tabs. + Active state already paints color + soft pink; this is just a hint that + the inactive label is interactive. */ +.tab:hover:not(.active) { + color: var(--text); + background: rgba(255,255,255,.04); +} + +/* NEW: visible keyboard focus ring on the tab itself. + Global :focus-visible at components.css L1110-L1116 already paints an + outline on every focusable element, but tabs sit on a tinted background + so we bump contrast with an inset ring that respects the brand color. */ +.tab:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 2px; +} + +/* NEW: focus ring on the tabpanel so screen-reader/keyboard users see where + focus has landed after activating a tab. The panel is tabindex=0 so it can + receive focus and be scrolled by the user with arrow keys. */ +.tabs-block [role="tabpanel"]:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 4px; + border-radius: var(--radius-xs, 10px); +} + +/* NEW: respect reduced motion for the tab transition. */ +@media (prefers-reduced-motion: reduce) { + .tab { transition: none; } +} + + +/* ---------- Mermaid -------------------------------------------------------- */ +/* NEW: .mermaid-* namespace introduced by MermaidDiagram.tsx. Article column + is 72ch (~720px) so we let the diagram fill width and scroll horizontally + only when its intrinsic width truly exceeds the column. */ +.mermaid-figure { + margin: 24px 0; + padding: 16px; + border: 1px solid var(--border); + border-radius: var(--radius-md, 20px); + background: rgba(255,255,255,.03); + overflow-x: auto; +} + +.mermaid-diagram { + display: flex; + justify-content: center; +} + +.mermaid-diagram > svg { + max-width: 100%; + height: auto; +} + +.mermaid-caption { + margin-top: 10px; + font-size: 13px; + line-height: 1.55; + color: var(--text-muted); + text-align: center; +} + +.mermaid-loading { + position: relative; + width: 100%; + color: var(--text-soft); + font-size: 13px; +} +.mermaid-loading::after { + content: "Loading diagram\2026"; +} + +@media (prefers-reduced-motion: reduce) { + .mermaid-loading::after { content: "Loading diagram"; } +} + + +/* TOKEN PROPOSALS */ +/* (none). The existing --primary, --primary-soft, --primary-border, + --info, --success, --warning, --error, --text-muted, --border, --radius-* + tokens cover every selector above. If we later want named aside tints, + propose: + --aside-tint-info: rgba(147,197,253,.045); + --aside-tint-success: rgba(99,230,190,.045); + --aside-tint-warning: rgba(255,209,102,.045); + --aside-tint-error: rgba(255,107,107,.05); +*/ diff --git a/.scratch/ui-ux/site-chrome.css b/.scratch/ui-ux/site-chrome.css new file mode 100644 index 0000000..5f26b0b --- /dev/null +++ b/.scratch/ui-ux/site-chrome.css @@ -0,0 +1,101 @@ +/* === site-chrome === */ +/* Site chrome polish: focus rings, hover transitions, mobile touch targets, + footer link affordances. All changes reuse existing tokens. */ + +/* APPEND-TO: .brand (components.css L96) */ +/* Add an explicit focus-visible radius so the global 2px outline wraps + cleanly around the inline-flex brand bounding box instead of clipping + into the mark and product slug. */ +.brand { + border-radius: 10px; +} + +/* NEW */ +/* Match the focus ring radius to the brand's own corner so the ring + visually hugs the element on keyboard focus. */ +.brand:focus-visible { + outline-offset: 4px; +} + +/* REPLACES: components.css L1123-L1131 */ +/* Icon-only header action (GitHub). Adds smooth hover transition for + color and background per ui-ux-pro-max duration-timing rule (200ms). + Global @media (prefers-reduced-motion: reduce) zeroes this transition. */ +.icon-link { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + color: var(--text-muted); + border-radius: 8px; + transition: color 200ms ease, background-color 200ms ease; +} + +/* APPEND-TO: .nav-action (components.css L1148) */ +/* Smooth hover transition for the Examples / Sponsor text actions. */ +.nav-action { + border-radius: 6px; + transition: color 200ms ease; +} + +/* APPEND-TO: .nav-tabs a (components.css L57) */ +/* Smooth color + underline transition when switching active product tab. */ +.nav-tabs a { + transition: color 200ms ease, border-bottom-color 200ms ease; +} + +/* APPEND-TO: .navlinks a (implicit, components.css L131) */ +/* Smooth fade between muted and primary text colors on navlink hover. */ +.navlinks a { + transition: color 200ms ease; +} + +/* NEW */ +/* Mobile touch target floor for header text actions and product tabs. + ui-ux-pro-max touch-target-size requires minimum 44x44px. The existing + 767px breakpoint at the bottom of components.css already handles + .btn-sm, .tab, and .icon-link. Extending coverage to .nav-action and + .nav-tabs a so every header touch target meets the floor. */ +@media (max-width: 767px) { + .nav-action { + min-height: 44px; + display: inline-flex; + align-items: center; + padding: 8px 10px; + } + .nav-tabs a { + min-height: 44px; + display: inline-flex; + align-items: center; + padding: 10px 0; + } +} + +/* NEW */ +/* Footer link affordance. The Footer template renders + <a class="site-footer-link"> for the author name; without an explicit + rule it inherits the global link color (primary) and has no hover or + focus feedback. Mirror the navlinks pattern: muted at rest, full text + on hover, smooth color transition, rounded focus ring. */ +.site-footer-link { + color: var(--text); + text-decoration: underline; + text-decoration-color: var(--border-strong); + text-underline-offset: 3px; + border-radius: 4px; + transition: color 200ms ease, text-decoration-color 200ms ease; +} +.site-footer-link:hover { + color: var(--primary-light); + text-decoration-color: var(--primary-border); +} + +/* NEW */ +/* The Footer icon-link sits in a flex row alongside two <p> tags. The + site-footer-inner uses space-between, so the GitHub anchor naturally + anchors left. On focus, give it a slightly larger offset so the ring + does not collide with the surrounding text baseline. */ +.site-footer .icon-link:focus-visible { + outline-offset: 3px; +} diff --git a/.scratch/ui-ux/ui-primitives.css b/.scratch/ui-ux/ui-primitives.css new file mode 100644 index 0000000..e9fefe5 --- /dev/null +++ b/.scratch/ui-ux/ui-primitives.css @@ -0,0 +1,309 @@ +/* === ui-primitives === */ +/* Surgical UX hardening for buttons, cards, alerts, badges, chips, grids. + No new variants. Preserves component APIs. Respects prefers-reduced-motion. + References: + - WCAG 2.1 AA contrast, focus-visible + - ui-ux-pro-max: focus-states, disabled-states, loading-states, + hover-states, cursor-pointer, reduced-motion, touch-target-size, + content-jumping (no transform/scale hover that shifts layout) */ + +/* --------------------------------------------------------------------------- + BUTTONS + --------------------------------------------------------------------------- */ + +/* REPLACES: components.css L372-385 + - Adds a smooth color/border/shadow transition so hover does not snap. + - Adds default `vertical-align: middle` so inline btns sit cleanly next to text. + - Adds `user-select: none` and `text-decoration: none` (anchors). */ +.btn { + display: inline-flex; + min-height: 42px; + align-items: center; + justify-content: center; + gap: 8px; + padding: 0 18px; + border-radius: 999px; + border: 1px solid var(--border-strong); + background: rgba(255, 255, 255, .04); + color: var(--text); + font-size: 14px; + font-weight: 700; + text-decoration: none; + user-select: none; + vertical-align: middle; + transition: background-color 160ms ease-out, border-color 160ms ease-out, + color 160ms ease-out, box-shadow 160ms ease-out; +} + +/* NEW + Generic hover for secondary/ghost/danger. Primary keeps its own rule below. + Uses color/border only (no transform) so layout never shifts. */ +.btn:hover { + background: rgba(255, 255, 255, .07); + border-color: var(--border-strong); + color: var(--text); +} + +/* NEW + Distinct focus-visible ring tuned for pink-on-dark. + Globals apply a 2px var(--primary) outline; on .btn-primary that ring would + blend into the pink fill. Use a layered ring: 2px dark gap + 2px pink halo. */ +.btn:focus-visible { + outline: none; + box-shadow: + 0 0 0 2px var(--bg), + 0 0 0 4px var(--primary-light); +} + +/* NEW + Pressed-state feedback. Color shift only, no scale (avoids content jump). */ +.btn:active:not(:disabled):not([aria-disabled="true"]) { + background: rgba(255, 255, 255, .09); +} + +/* REPLACES: components.css L387-394 + - Keeps the pink fill and shadow. + - Tightens hover: brighten + slightly stronger shadow. + - Removes default text-shadow inheritance from page. */ +.btn-primary { + background: var(--primary); + border-color: var(--primary); + color: #0F0F12; + box-shadow: 0 14px 38px rgba(239, 71, 121, .26); +} +.btn-primary:hover { + background: var(--primary-light); + border-color: var(--primary-light); + color: #0F0F12; + box-shadow: 0 14px 38px rgba(239, 71, 121, .34); +} +.btn-primary:active:not(:disabled):not([aria-disabled="true"]) { + background: var(--primary); + border-color: var(--primary); + box-shadow: 0 10px 28px rgba(239, 71, 121, .28); +} + +/* APPEND-TO: .btn-secondary (components.css L396-398) + Hover surfaces lift to --surface-hover so the affordance is unambiguous. */ +.btn-secondary:hover { + background: var(--surface-hover); + border-color: var(--border-strong); +} + +/* APPEND-TO: .btn-ghost (components.css L400-402) + Ghost needs a visible hover or it looks dead. */ +.btn-ghost:hover { + background: rgba(255, 255, 255, .05); +} + +/* APPEND-TO: .btn-danger (components.css L404-408) */ +.btn-danger:hover { + background: rgba(255, 107, 107, .16); + border-color: rgba(255, 107, 107, .42); + color: #FFD4D4; +} + +/* REPLACES: components.css L1120 + Disabled visual state for both <button disabled> and <a aria-disabled="true">. + Loses pointer affordance, dims, and kills hover/shadow transitions. */ +.btn:disabled, +.btn[aria-disabled="true"], +.tab:disabled, +.tab[aria-disabled="true"] { + cursor: not-allowed; + opacity: 0.55; + box-shadow: none; + pointer-events: none; +} + +/* NEW + Loading-state convention. Replace label with an inline spinner without + resizing the button. The button keeps its width via `position: relative` + on the host and an absolutely-positioned spinner. We hide the slot text + visually (kept in DOM for SR + aria-busy announces in-flight state). */ +.btn-loading { + position: relative; + cursor: progress; + pointer-events: none; + color: transparent !important; +} +.btn-loading::after { + content: ""; + position: absolute; + width: 16px; + height: 16px; + border-radius: 50%; + border: 2px solid currentColor; + border-top-color: transparent; + color: var(--text); + animation: btn-spin 720ms linear infinite; +} +.btn-primary.btn-loading::after { color: #0F0F12; } +.btn-danger.btn-loading::after { color: #FFB4B4; } + +@keyframes btn-spin { + to { transform: rotate(360deg); } +} + +@media (prefers-reduced-motion: reduce) { + .btn { transition: none; } + .btn-loading::after { animation-duration: 1800ms; } +} + +/* --------------------------------------------------------------------------- + CARDS + --------------------------------------------------------------------------- */ + +/* APPEND-TO: .card (components.css L232-238) + Adds default color + transition so an interactive card can animate cleanly + without changing static-card visuals. */ +.card { + color: inherit; + transition: background-color 180ms ease-out, border-color 180ms ease-out, + box-shadow 180ms ease-out; +} + +/* NEW + Interactive card variant (used when Card is rendered as <a> or with + `interactive` prop. Provides cursor, hover, and focus affordance without + layout shift (color/border/shadow only, no scale). */ +.card-interactive { + cursor: pointer; + text-decoration: none; +} +.card-interactive:hover { + background: rgba(255, 255, 255, .055); + border-color: var(--border-strong); + box-shadow: 0 10px 30px rgba(0, 0, 0, .28); +} +.card-interactive:focus-visible { + outline: none; + border-color: var(--primary-border); + box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--primary-light); +} + +@media (prefers-reduced-motion: reduce) { + .card { transition: none; } +} + +/* --------------------------------------------------------------------------- + ALERTS + --------------------------------------------------------------------------- */ + +/* APPEND-TO: .alert (components.css L464-472) + Alert link focus alignment. The body anchor uses a border-bottom underline; + on focus-visible promote the underline to the brand color so keyboard users + see exactly which inline link is active without the global pink outline + clashing with the surrounding card. */ +.alert-body a:focus-visible { + outline: none; + border-bottom-color: var(--primary); + background: var(--primary-soft); + border-radius: 2px; +} + +/* --------------------------------------------------------------------------- + BADGES & CHIPS + --------------------------------------------------------------------------- */ + +/* REPLACES: components.css L417-428 + - Default badge text was `var(--text-muted)` (#A1A1AA) on a near-bg surface. + Bumps to a slightly stronger value to clear 4.5:1 against the dark canvas + even when stacked on the section gradient. + - Adds line-height + padding-y to give breathing room for taller glyphs. */ +.badge { + display: inline-flex; + min-height: 28px; + align-items: center; + border: 1px solid var(--border); + background: rgba(255, 255, 255, .045); + border-radius: 999px; + padding: 2px 10px; + font-size: 12px; + font-weight: 720; + line-height: 1.4; + color: var(--text); + letter-spacing: 0.01em; +} + +/* APPEND-TO: .chip (components.css L207-220) + Small line-height + vertical padding tweak so chips sit centered alongside + buttons in a btn-row without visual drift. */ +.chip { + line-height: 1.4; + padding-block: 2px; +} + +/* --------------------------------------------------------------------------- + GRID + --------------------------------------------------------------------------- */ + +/* APPEND-TO: .grid (components.css L222-225) + Hard `repeat(N, 1fr)` breaks below ~640px (text gets squeezed below 16px). + Collapse 3/4/5 columns to 2 at <=768px and to 1 at <=520px. Keeps cols-2 + at >=520px. Logical (not transform) layout, no content jump on resize. */ +@media (max-width: 768px) { + .grid.cols-3, + .grid.cols-4, + .grid.cols-5 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} +@media (max-width: 520px) { + .grid.cols-2, + .grid.cols-3, + .grid.cols-4, + .grid.cols-5 { + grid-template-columns: minmax(0, 1fr); + } +} + +/* --------------------------------------------------------------------------- + SECTION + HERO + --------------------------------------------------------------------------- */ + +/* APPEND-TO: .section (components.css L172-181) + Reduce horizontal padding on small screens so sections do not bleed past + container gutters. No vertical-rhythm change. */ +@media (max-width: 640px) { + .section { + padding: 24px 18px; + border-radius: var(--radius-lg); + } +} + +/* APPEND-TO: .hero (components.css L133-138) + Hero top padding is large for desktop. Tighten on small screens so the + first interactive control stays near the fold. */ +@media (max-width: 640px) { + .hero { + padding: 56px 0 36px; + } +} + +/* --------------------------------------------------------------------------- + FORMS (.field) + --------------------------------------------------------------------------- */ + +/* APPEND-TO: input/select/textarea focus (components.css L459-462) + The custom inset focus halo already provides a strong indicator; ensure the + default browser outline stays suppressed on focus-visible too (globals.css + already does this, but reinforce locally so future global resets do not + double-ring the field). */ +.field :is(input, select, textarea):focus-visible { + outline: none; +} + +/* =========================================================================== + TOKEN PROPOSALS (do not apply, for review against tokens.css upstream) + =========================================================================== + 1. --focus-ring-offset-color: var(--bg) + Used by the layered focus ring (gap-then-halo). Today inlined as var(--bg) + in .btn:focus-visible and .card-interactive:focus-visible. + 2. --transition-fast: 160ms ease-out + Used by .btn, .card transitions. A token would let us tune system-wide + motion without searching files. + 3. --shadow-btn-primary: 0 14px 38px rgba(239,71,121,.26) + Currently inlined in .btn-primary; a token would harmonize with the + pricing-card featured glow which uses a similar pink shadow. + =========================================================================== */ diff --git a/VSDD.md b/VSDD.md new file mode 100644 index 0000000..dec24d9 --- /dev/null +++ b/VSDD.md @@ -0,0 +1,230 @@ +# **Verified Spec-Driven Development (VSDD)** + +## **The Fusion: VDD × TDD × SDD for AI-Native Engineering** + +### **Overview** + +**Verified Spec-Driven Development (VSDD)** is a unified software engineering methodology that fuses three proven paradigms into a single AI-orchestrated pipeline: + +- **Spec-Driven Development (SDD):** Define the contract before writing a single line of implementation. Specs are the source of truth. +- **Test-Driven Development (TDD):** Tests are written *before* code. Red → Green → Refactor. No code exists without a failing test that demanded it. +- **Verification-Driven Development (VDD):** Subject all surviving code to adversarial refinement until a hyper-critical reviewer is forced to hallucinate flaws. + +VSDD treats these not as competing philosophies but as **sequential gates** in a single pipeline. Specs define *what*. Tests enforce *how*. Adversarial verification ensures *nothing was missed*. AI models orchestrate every phase, with the human developer serving as the strategic decision-maker and final authority. + +--- + +### **I. The VSDD Toolchain** + +| Role | Entity | Function | +| --- | --- | --- | +| **The Architect** | Human Developer | Strategic vision, domain expertise, acceptance authority. Signs off on specs, arbitrates disputes between Builder and Adversary. | +| **The Builder** | Claude (or similar) | Spec authorship, test generation, code implementation, and refactoring. Operates under strict TDD constraints. | +| **The Tracker** | **Chainlink** | Hierarchical issue decomposition — Epics → Issues → Sub-issues ("beads"). Every spec, test, and implementation maps to a bead. | +| **The Adversary** | **Sarcasmotron** (Gemini Gem or equivalent) | Hyper-critical reviewer with zero patience. Reviews specs, tests, *and* implementation. Fresh context on every pass. | + +--- + +### **II. The VSDD Pipeline** + +#### **Phase 1 — Spec Crystallization** + +*Nothing gets built until the contract is airtight — and the architecture is verification-ready by design.* + +The human developer describes the feature intent to the Builder. The Builder then produces a **formal specification document** for each unit of work. Critically, this phase doesn't just define *what* the software does — it defines *what must be provable about it* and structures the architecture accordingly. + +**Step 1a: Behavioral Specification** + +The Builder produces the functional contract: + +- **Behavioral Contract:** What the module/function/endpoint *must* do, expressed as preconditions, postconditions, and invariants. +- **Interface Definition:** Input types, output types, error types. No ambiguity. If it's an API, this is the OpenAPI/GraphQL schema. If it's a module, this is the type signature and doc contract. +- **Edge Case Catalog:** Explicitly enumerated boundary conditions, degenerate inputs, and failure modes. The Builder is prompted to be *exhaustive* here — "What happens when the input is null? Empty? Maximum size? Negative? Unicode? Concurrent?" +- **Non-Functional Requirements:** Performance bounds, memory constraints, security considerations baked into the spec itself. + +**Step 1b: Verification Architecture** + +Before any implementation design is finalized, the Builder produces a **Verification Strategy** that answers: *"What properties of this system must be mathematically provable, and what architectural constraints does that impose?"* + +This includes: + +- **Provable Properties Catalog:** Which invariants, safety properties, and correctness guarantees must be formally verified — not just tested? Examples: "This state machine can never reach an invalid state." "This arithmetic can never overflow." "This parser always terminates." "This access control check is never bypassed." The Builder distinguishes between properties that *should* be proven (critical path, security boundaries, financial calculations) and properties where test coverage is sufficient (UI formatting, logging, non-critical defaults). +- **Purity Boundary Map:** A clear architectural separation between the **deterministic, side-effect-free core** (where formal verification can operate) and the **effectful shell** (I/O, network, database, user interaction). This is the most consequential design decision in VSDD — it dictates module boundaries, dependency direction, and how state flows through the system. The pure core must be designed so that verification tools can reason about it without mocking the entire universe. +- **Verification Tooling Selection:** Based on the language and the properties to be proven, the Builder selects the appropriate formal verification stack (Kani for Rust, CBMC for C/C++, Dafny, TLA+ for distributed systems, etc.) and identifies any constraints these tools impose on code structure. This happens *now*, not after the code is written, because tool constraints are architectural constraints. +- **Property Specifications:** Where possible, the Builder drafts the actual formal property definitions (e.g., Kani proof harnesses, Dafny contracts, TLA+ invariants) alongside the behavioral spec. These aren't implementation — they're the formal expression of what the spec already says in natural language. They serve as a second, mathematically precise encoding of the requirements. + +**Why this must happen in Phase 1:** If the system is designed with side effects woven through the core logic, no amount of Phase 5 heroics will make it verifiable. A function that reads from a database, performs a calculation, and writes to a log in one block cannot be formally verified without mocking infrastructure that the verifier may not support. But a function that takes data in, returns a result, and lets the caller handle persistence — that's a function a model checker can reason about. This boundary must be drawn at the spec level because it fundamentally shapes the module decomposition, the dependency graph, and the testing strategy that follows. + +**Step 1c: Spec Review Gate** + +The complete spec — behavioral contracts *and* verification architecture — is reviewed by *both* the human and the Adversary before any tests are written. Sarcasmotron tears into the spec looking for: + +- Ambiguous language that could be interpreted multiple ways +- Missing edge cases +- Implicit assumptions that aren't stated +- Contradictions between different parts of the spec +- **Properties claimed as "testable only" that should be provable** (the Adversary pushes back on lazy verification boundaries) +- **Purity boundary violations** — logic marked as "pure core" that actually depends on external state +- **Verification tool mismatches** — properties the selected tooling can't actually prove + +The spec is iterated until the Adversary can't find legitimate holes in either the behavioral contract or the verification strategy. + +**Chainlink Integration:** Each spec maps to a Chainlink Issue. Sub-issues are generated for each behavioral contract item, edge case, non-functional requirement, *and* each formally provable property. The provable properties get their own bead chain so their status is tracked independently from test coverage. + +--- + +#### **Phase 2 — Test-First Implementation (The TDD Core)** + +*Red → Green → Refactor, enforced by AI.* + +With an airtight spec in hand, the Builder now writes tests — and *only* tests. No implementation code yet. + +**Step 2a: Test Suite Generation** + +The Builder translates the spec directly into executable tests: + +- **Unit Tests:** One or more tests per behavioral contract item. Every postcondition becomes an assertion. Every precondition violation becomes a test that expects a specific error. +- **Edge Case Tests:** Every item in the Edge Case Catalog becomes a test. These are the tests that catch the bugs that "never happen in production" (until they do). +- **Integration Tests:** Tests that verify the module works correctly within the larger system context defined in the spec. +- **Property-Based Tests:** Where applicable, the Builder generates property-based tests (e.g., using Hypothesis, fast-check, or proptest) that assert invariants hold across randomized inputs. + +**The Red Gate:** All tests must *fail* before any implementation begins. If a test passes without implementation, the test is suspect — it's either testing the wrong thing or the spec was wrong. The Builder flags this for human review. + +**Step 2b: Minimal Implementation** + +The Builder writes the *minimum* code necessary to make each test pass, one at a time. This is classic TDD discipline: + +1. Pick the next failing test. +2. Write the smallest implementation that makes it pass. +3. Run the full suite — nothing else should break. +4. Repeat. + +**Step 2c: Refactor** + +After all tests are green, the Builder refactors for clarity, performance, and adherence to the non-functional requirements in the spec. The test suite acts as the safety net — if refactoring breaks something, the tests catch it immediately. + +**Human Checkpoint:** The developer reviews the test suite and implementation for alignment with the "spirit" of the spec. AI can miss intent even when it nails the letter of the contract. + +--- + +#### **Phase 3 — Adversarial Refinement (The VDD Roast)** + +*The code survived testing. Now it faces the gauntlet.* + +The verified, test-passing codebase — along with the spec and test suite — is presented to **Sarcasmotron** in a fresh context window. + +**What the Adversary reviews:** + +1. **Spec Fidelity:** Does the implementation actually satisfy the spec, or did the tests inadvertently encode a misunderstanding? +2. **Test Quality:** Are the tests actually testing what they claim? Are there tests that would pass even if the implementation were subtly wrong? (Tautological tests, tests that mock too aggressively, tests that assert on implementation details rather than behavior.) +3. **Code Quality:** The classic VDD roast — placeholder comments, generic error handling, inefficient patterns, hidden coupling, missing resource cleanup, race conditions. +4. **Security Surface:** Input validation gaps, injection vectors, authentication/authorization assumptions. +5. **Spec Gaps Revealed by Implementation:** Sometimes writing the code reveals that the spec was incomplete. The Adversary looks for implemented behavior that isn't covered by the spec. + +**Negative Prompting:** Sarcasmotron is prompted for zero tolerance. No "overall this looks good, but..." preamble. Every piece of feedback is a concrete flaw with a specific location and a proposed fix or question. + +**Context Reset:** Fresh context window on every adversarial pass. No relationship drift. No accumulated goodwill. + +--- + +#### **Phase 4 — Feedback Integration Loop** + +The Adversary's critique feeds back through the entire pipeline: + +- **Spec-level flaws** → Return to Phase 1. Update the spec, re-review. +- **Test-level flaws** → Return to Phase 2a. Fix or add tests, verify they fail against the current implementation (or a deliberately broken version), then fix implementation if needed. +- **Implementation-level flaws** → Return to Phase 2c. Refactor, ensure all tests still pass. +- **New edge cases discovered** → Add to spec's Edge Case Catalog, write new failing tests, implement fixes. + +This loop continues until convergence (see Phase 6). + +--- + +#### **Phase 5 — Formal Hardening (Executing the Verification Plan)** + +The verification architecture designed in Phase 1b is now *executed* against the battle-tested implementation. Because the codebase was architected from the start with a pure core and clear purity boundaries, formal verification tools can operate on it without heroic refactoring. + +- **Proof Execution:** The property specifications drafted in Phase 1b (Kani harnesses, Dafny contracts, TLA+ invariants, etc.) are run against the implementation. Because the architecture was designed for verifiability, these proofs should engage cleanly with the pure core. Failures here indicate either implementation bugs or spec properties that need refinement — both feed back through Phase 4. +- **Fuzz Testing:** Structured fuzzing (AFL++, libFuzzer, cargo-fuzz) is layered on top of property-based tests to find inputs that no human or AI anticipated. The deterministic core is an ideal fuzz target because it has no environmental dependencies to mock. +- **Security Hardening:** Suites like **Wycheproof** (cryptographic edge cases) and **Semgrep** (static analysis) are run as CI/CD gates. +- **Mutation Testing:** Tools like **mutmut** or **Stryker** mutate the code to verify the test suite actually catches real bugs. If a mutation survives, the test suite has a gap. +- **Purity Boundary Audit:** A final check that the purity boundaries defined in Phase 1b have been respected throughout implementation. Any side effects that crept into the pure core during development are flagged and refactored out. + +All formal verification and fuzzing results feed back into Phase 4 if issues are found. + +--- + +#### **Phase 6 — Convergence (The Exit Signal)** + +VSDD inherits VDD's **hallucination-based termination**, extended across all three dimensions: + +| Dimension | Convergence Signal | +| --- | --- | +| **Spec** | The Adversary's spec critiques are nitpicks about wording, not about missing behavior, ambiguity, or verification gaps. | +| **Tests** | The Adversary can't identify a meaningful untested scenario. Mutation testing confirms high kill rate. | +| **Implementation** | The Adversary is forced to invent problems that don't exist in the code. | +| **Verification** | All properties from the Phase 1b catalog pass formal proof. Fuzzers find nothing. Purity boundaries are intact. | + +**Maximum Viable Refinement** is reached when all four dimensions have converged. The software is considered **Zero-Slop** — every line of code traces to a spec requirement, is covered by a test, has survived adversarial scrutiny, and the critical path is formally proven. + +--- + +### **III. The VSDD Contract Chain** + +One of VSDD's defining properties is **full traceability**. Every artifact links back: + +``` +Spec Requirement → Verification Property → Chainlink Bead → Test Case → Implementation → Adversarial Review → Formal Proof +``` + +At any point, you can ask: *"Why does this line of code exist?"* and trace it all the way back to a specific spec requirement, through the verification property it satisfies, the test that demanded it, the adversarial review that hardened it, and the formal proof that guarantees it. Equally, you can ask *"Why is this module structured as a pure function?"* and trace that decision back to the Purity Boundary Map in Phase 1b. + +--- + +### **IV. Core Principles of VSDD** + +1. **Spec Supremacy:** The spec is the highest authority below the human developer. Tests serve the spec. Code serves the tests. Nothing exists without a reason traced to the spec. + +2. **Verification-First Architecture:** The need for formal provability shapes the design, not the other way around. Pure core, effectful shell. If you can't verify it, you architected it wrong — and you find that out in Phase 1, not Phase 5. + +3. **Red Before Green:** No implementation code is written until a failing test demands it. AI models are explicitly constrained to follow TDD discipline — no "let me just write the whole thing and add tests after." + +3. **Anti-Slop Bias:** The first "correct" version is assumed to contain hidden debt. Trust is earned through adversarial survival, not initial appearance. + +4. **Forced Negativity:** Adversarial pressure bypasses the politeness filters of standard LLM interactions. The Adversary doesn't care about your feelings — it cares about your invariants. + +5. **Linear Accountability:** Chainlink beads ensure every spec item, test, and line of code has a corresponding tracked unit of work. Nothing slips through the cracks. + +6. **Entropy Resistance:** Context resets on every adversarial pass prevent the natural degradation of long-running AI conversations. + +7. **Four-Dimensional Convergence:** The system isn't done until specs, tests, implementation, *and* formal proofs have all independently survived adversarial review. + +--- + +### **V. AI Orchestration Notes** + +VSDD is explicitly designed for multi-model AI workflows: + +- **The Builder** benefits from large context windows and strong code generation (Claude, GPT-4, etc.). It needs to hold the full spec, test suite, and implementation simultaneously. +- **The Adversary** benefits from a *different* model or configuration to avoid shared blind spots. Using a different model family (e.g., Gemini as Adversary when Claude is Builder) introduces genuine cognitive diversity. +- **The Human** is not a bottleneck — they're the strategic layer. They approve specs, resolve disputes, and make judgment calls that AI can't. The human's role is *elevated*, not diminished, by the AI orchestration. + +**Prompt Engineering for TDD Discipline:** The Builder must be explicitly instructed: *"You are operating under strict TDD. Write tests FIRST. Do NOT write implementation code until I confirm all tests fail. When implementing, write the MINIMUM code to pass each test."* Without this constraint, AI models will naturally try to write implementation and tests simultaneously. + +--- + +### **VI. When to Use VSDD** + +VSDD is high-ceremony by design. It's worth the overhead when: + +- Correctness is non-negotiable (financial systems, medical software, infrastructure) +- The codebase will be maintained long-term and must resist entropy +- Multiple AI models are available and the team wants maximum quality extraction +- Security is a primary concern, not an afterthought +- The project complexity justifies formal spec work + +For rapid prototyping or throwaway scripts, use the parts that make sense — TDD discipline and a quick adversarial pass can still catch a lot of slop even without the full ceremony. + +--- + +*"VSDD doesn't just generate code — it generates code that can prove why it exists, demonstrate that it works, and survive an adversary that wants it dead."* diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 0000000..6fb9e5d --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,51 @@ +import { defineConfig } from "astro/config"; +import mdx from "@astrojs/mdx"; +import react from "@astrojs/react"; +import sitemap from "@astrojs/sitemap"; +import expressiveCode from "astro-expressive-code"; +import remarkGfm from "remark-gfm"; +import { readFileSync } from "node:fs"; +import { esmDependencies } from "./scripts/esm-dependencies.mjs"; + +const outputDirectory = new URL("./dist/", import.meta.url); + +export default defineConfig({ + site: "https://suites.dev", + // The live site is slash-canonical. Netlify Pretty URLs serves and + // redirects to that form, so links, canonical tags and the sitemap have + // to agree with it or every page redirects away from its own canonical URL. + trailingSlash: "always", + output: "static", + outDir: "./dist", + vite: { + optimizeDeps: { include: ["@docsearch/react"] }, + ssr: { + // Bundle the renderer's ESM graph so legacy host dependencies cannot + // shadow it. Native CommonJS dependencies keep their normal loader. + noExternal: ["@docsearch/react", ...esmDependencies("astro-expressive-code", import.meta.url)], + }, + plugins: [{ + name: "suites-dependency-cache", + // Builds must not replace dependency files used by a running dev server. + config: (_config, { mode }) => ({ cacheDir: `node_modules/.vite-${mode}` }), + }], + }, + integrations: [ + expressiveCode(), + mdx({ + remarkPlugins: [remarkGfm], + }), + react(), + sitemap({ + // Read the rendered directive so content-level no_index and previews agree + // with the sitemap. Status pages are already excluded by the integration. + filter: (page) => { + const pathname = new URL(page).pathname; + const route = pathname.endsWith('/') ? pathname : `${pathname}/`; + const html = readFileSync(new URL(`.${route}index.html`, outputDirectory), "utf8"); + const head = html.split("</head>")[0]; + return !/<meta\b[^>]*name="robots"[^>]*content="[^"]*noindex/i.test(head); + }, + }), + ], +}); diff --git a/config/algolia-config.js b/config/algolia-config.mjs similarity index 81% rename from config/algolia-config.js rename to config/algolia-config.mjs index 27b4ac6..c4c7d03 100644 --- a/config/algolia-config.js +++ b/config/algolia-config.mjs @@ -1,11 +1,11 @@ -module.exports = { +export default { appId: 'OR8PNMV0IL', apiKey: '28a25b54e18eb96f17089adf1f3ed295', indexName: 'suites', rateLimit: 8, maxDepth: 10, startUrls: ['https://suites.dev', 'https://suites.dev/docs'], - sitemaps: ['https://suites.dev/sitemap.xml'], + sitemaps: ['https://suites.dev/sitemap-index.xml'], discoveryPatterns: ['https://suites.dev/docs/**'], externalUrlRegex: 'suites\\.dev', actions: [ @@ -13,13 +13,13 @@ module.exports = { indexName: 'suites', pathsToMatch: ['https://suites.dev/docs/**'], recordExtractor: ({ $, helpers }) => { - // priority order: deepest active sub list header -> navbar active item -> 'Documentation' - const lvl0 = - $( - '.menu__link.menu__link--sublist.menu__link--active, .navbar__item.navbar__link--active' - ) - .last() - .text() || 'Documentation'; + // Navigation is excluded from article records. Keep the current sidebar + // section as the top-level search category before removing page chrome. + const active = $('.sidebar-nav a[aria-current="page"]').first(); + const lvl0 = active.closest('.sidebar-group').find('summary span').first().text().trim() + || active.text().trim() || 'Documentation'; + if (!$('article.article').length) return []; + $('.breadcrumb, .doc-pager, .doc-edit-link, .legacy-anchor').remove(); return helpers.docsearch({ recordProps: { @@ -27,7 +27,7 @@ module.exports = { selectors: '', defaultValue: lvl0, }, - lvl1: ['header h1', 'article h1'], + lvl1: 'article h1', lvl2: 'article h2', lvl3: 'article h3', lvl4: 'article h4', diff --git a/config/babel.config.js b/config/babel.config.js deleted file mode 100644 index e00595d..0000000 --- a/config/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: [require.resolve('@docusaurus/core/lib/babel/preset')], -}; diff --git a/config/docs-sidebars.js b/config/docs-sidebars.js deleted file mode 100644 index 82f1119..0000000 --- a/config/docs-sidebars.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Creating a sidebar enables you to: - - create an ordered group of docs - - render a sidebar for each doc of that group - - provide next/previous navigation - - The sidebars can be generated from the filesystem, or explicitly defined here. - - Create as many sidebars as you want. - */ - -// @ts-check - -/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ -const sidebars = { - tutorialSidebar: [ - { - type: "autogenerated", - dirName: ".", - }, - ], -}; - -module.exports = sidebars; diff --git a/config/prisma-theme.config.js b/config/prisma-theme.config.js deleted file mode 100644 index 7b6200e..0000000 --- a/config/prisma-theme.config.js +++ /dev/null @@ -1,65 +0,0 @@ -export const draculaThemeConfig = { - theme: { - plain: { - color: "#f8f8f2", - backgroundColor: "#16181d" - }, - styles: [ - { - types: ["comment", "prolog", "doctype", "cdata"], - style: { - color: "#6272a4", - fontStyle: "italic" - } - }, - { - types: ["namespace"], - style: { - opacity: 0.7 - } - }, - { - types: ["string", "attr-value"], - style: { - color: "#977cdc" - } - }, - { - types: ["punctuation", "operator"], - style: { - color: "#f8f8f2" - } - }, - { - types: ["entity", "url", "symbol", "number", "boolean", "variable", "constant", "property", "regex", "inserted"], - style: { - color: "#50fa7b" - } - }, - { - types: ["atrule", "keyword", "attr-name", "selector"], - style: { - color: "#77b7d7" - } - }, - { - types: ["function", "deleted", "tag"], - style: { - color: "#86d9ca" - } - }, - { - types: ["function-variable"], - style: { - color: "#bd93f9" - } - }, - { - types: ["tag", "selector", "keyword"], - style: { - color: "#77b7d7" - } - } - ] - } -}; diff --git a/docs/api-reference/types.md b/docs/api-reference/types.md deleted file mode 100644 index b53cb4c..0000000 --- a/docs/api-reference/types.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -sidebar_position: 6 -title: Types -description: TypeScript types and interfaces used in Suites ---- - -# Types - -TypeScript type definitions used throughout Suites. - -## Core Types - -### `Mocked<T>` - -A deeply nested mock type. All methods become mock functions, nested objects are mocked recursively. - -```typescript -interface UserService { - repository: { - find(id: string): User; - save(user: User): void; - }; - validate(user: User): boolean; -} - -const service: Mocked<UserService> = unitRef.get(UserService); - -// All methods mocked, including nested -service.validate.mockReturnValue(true); -service.repository.find.mockResolvedValue(testUser); -service.repository.save.mockReturnValue(undefined); -``` - -## How `Mocked<T>` Works: Module Augmentation - -The `Mocked<T>` type is **always imported from `@suites/unit`**, never from adapter packages. Suites uses TypeScript's module augmentation to automatically provide the correct library-specific types. - -### Single Import Point - -```typescript -// ✅ Always import from @suites/unit -import { Mocked } from '@suites/unit'; - -// ❌ Never import from adapter packages -// import { Mocked } from '@suites/doubles.jest'; // Wrong! -``` - -### Automatic Type Resolution - -When an adapter package (like `@suites/doubles.jest`) is installed, it automatically augments the `@suites/unit` module with library-specific types: - -```typescript -// @suites/doubles.jest/unit.d.ts (internal - you never see this) -declare module '@suites/unit' { - export type Mocked<T> = jest.Mocked<T>; // Overrides base type -} -``` - -This means the same `Mocked<T>` import resolves to different concrete types based on which adapter is installed. - -:::tip Zero Configuration -This works automatically when you install an adapter package. No manual configuration needed - TypeScript resolves the correct types based on your installed dependencies. -::: - -### Why This Architecture Matters - -**Because Suites is framework agnostic**. The same test code works across Jest, Vitest, and Sinon without changes. Only the installed adapter package determines which library-specific types and methods are available. - -**Maintains Dependency Inversion**: The `@suites/unit` package never depends on adapter packages. Instead, adapters augment the base package - following proper dependency inversion principle. - -```typescript -// This exact code works with any adapter installed -describe('UserService', () => { - let service: UserService; - let database: Mocked<Database>; - - beforeAll(async () => { - const { unit, unitRef } = await TestBed.solitary(UserService).compile(); - service = unit; - - // Return Mocked type corresponding to installed adapter - database = unitRef.get(Database); - }); -}); -``` - -## See Also - -- [UnitReference](/docs/api-reference/unit-reference/) - Using the UnitReference interface -- [Mock Configuration](/docs/api-reference/mock-configuration/) - Configuring mocks with proper types -- [TypeScript Configuration](/docs/get-started/installation/#setting-up-tsconfigjson) - Setting up TypeScript for Suites \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md deleted file mode 100644 index 87a5b23..0000000 --- a/docs/changelog.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -sidebar_position: 6 -title: Changelog -description: Version history and release notes for Suites ---- - -# Changelog - -All notable changes to Suites are documented here. For the complete version history, visit our [GitHub Releases](https://github.com/suites-dev/suites/releases). - ---- - -## January 2, 2025 - -### 🐛 Bug Fixes in v3.0.1 - -We've released a patch update addressing several dependency and type resolution issues reported by the community. - -**What's Fixed:** -- ✅ Resolved `reflect-metadata` peer dependency conflicts affecting InversifyJS and NestJS adapters -- ✅ Fixed missing return statement when instantiating exposed classes -- ✅ Corrected InversifyJS interface imports to properly use type imports -- ✅ Cleaned up redundant imports across Jest, Vitest, and Sinon packages - -This release ensures smoother integration with popular DI frameworks and reduces TypeScript configuration friction. - -[View full release notes →](https://github.com/suites-dev/suites/releases/tag/v3.0.1) - ---- - -## July 13, 2024 - -### 🎉 Suites v3.0.0 - -We're incredibly excited to announce **Suites v3.0.0**, marking the official evolution from Automock to Suites. This -major release represents a significant milestone in the project's journey. - -#### 📜 From Automock to Suites - -Suites was previously known as **Automock** through version `2.x`. Starting with version `3.0.0`, the project has been -rebranded to Suites while maintaining the same powerful foundations and principles. Automock will continue to receive -critical bug fixes, but all new features and development efforts are now focused on Suites. - -#### ⚡ What's New - -**Sociable Testing Support** -```typescript -// New .sociable() method for flexible unit testing -const { unit } = await TestBed.solitary(UserService).compile(); -const { unit } = await TestBed.sociable(UserService).compile(); -``` - -**Modern Framework Support** -- 🧪 Official Vitest adapter for Vue and React ecosystems -- 📦 Full ES Module (ESM) support -- 🔄 Unified imports through `@suites/unit` regardless of test framework - -**Enhanced Type Safety** -```typescript -// New Mocked type with deep property mocking -const mockRepo: Mocked<UserRepository> = unitRef.get(UserRepository); -``` - -#### 💥 Breaking Changes - -This is a major version with important API changes: - -**Async Compilation Required** -```typescript -// ❌ Old (v2.x) -const { unit } = TestBed.create(UserService).compile(); - -// ✅ New (v3.x) -const { unit } = await TestBed.solitary(UserService).compile(); -``` - -**Method Renaming** -- `TestBed.create()` → `TestBed.solitary()` -- `.mock.using()` → `.mock.impl()` - -**Migration Support** - -We've prepared comprehensive migration guides to help you upgrade smoothly: -- [Migration from Automock →](/docs/migration-guides/from-automock/) -- Migration tool development in progress - -[View full release notes →](https://github.com/suites-dev/suites/releases/tag/v3.0.0) - ---- - -## Earlier History: Automock (2022-2023) - -Before becoming Suites at `v3.0.0`, the project was developed as **Automock** from `v1.0.0` through `v2.x`. During this period, the foundational architecture was established: - -- **`v2.1.0`** (Dec 2023): InversifyJS adapter support -- **`v2.0.0`** (Nov 2022): Native mocking implementation, Node.js v16+ requirement -- **`v1.x series`** (2022): Property injection support, enhanced dependency resolution - -The transition to Suites represented a rebranding and modernization while preserving the core testing philosophy and architectural foundations built during the Automock era. - -[View complete Automock release history →](https://github.com/suites-dev/suites/releases?q=v2&expanded=true) - ---- - -## Version Support - -| Version | Status | Released | Support Until | -|-------------------|------------------------|-----------|---------------| -| `v3.0.x` | 🟢 Stable | July 2024 | June 2026 | -| `v2.x` (Automock) | ⚠️ Critical fixes only | Nov 2022 | June 2025 | - -**Support Policy:** -- Stable versions receive security patches and critical bug fixes -- Migration guides provided for all major version upgrades -- Community support available through [GitHub Discussions](https://github.com/suites-dev/suites/discussions) - -## Get Involved - -We're grateful to our growing community of contributors and users. Your feedback shapes Suites' development. - -- 🐛 [Report issues](https://github.com/suites-dev/suites/issues) -- 💡 [Request features](https://github.com/suites-dev/suites/discussions) -- 📖 [Improve documentation](https://github.com/suites-dev/suites.dev) -- ⭐ [Star us on GitHub](https://github.com/suites-dev/suites) diff --git a/docs/get-started/index.md b/docs/get-started/index.md deleted file mode 100644 index d9bc488..0000000 --- a/docs/get-started/index.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -sidebar_position: 1 -title: Get Started -description: Everything you need to start using Suites in minutes ---- - -# Get Started -Learn how to install Suites, configure the testing environment, and write the first automated test suite. - -<div class="in-this-section"> - -### In This Section -- [**Why Suites?**](/docs/get-started/why-suites/) - Compare with manual mocking and explore what Suites provides -- [**Installation**](/docs/get-started/installation/) - Install and configure Suites -- [**Quick Start**](/docs/get-started/quickstart/) - Write the first test in 5 minutes -</div> - -## Prerequisites - -The following requirements must be met: - -- **Framework** - NestJS, InversifyJS, or plain TypeScript classes with constructor injection -- **Testing library** - Jest, Vitest, or Sinon - -:::tip Framework Flexibility -Suites supports the IoC principle through multiple implementations. - -Currently: dependency injection frameworks (NestJS, InversifyJS) via TestBed, or any TypeScript code via `mock()`/`stub()`. -::: - - -## Recommended Path - -For those new to Suites: - -1. **[Why Suites?](/docs/get-started/why-suites/)** - Explore what Suites provides -2. **[Installation](/docs/get-started/installation/)** - Set up the environment -3. **[Quick Start](/docs/get-started/quickstart/)** - Write the first test -4. **[Guides](/docs/guides/)** - Learn testing patterns - -## Resources - -- **[Suites Examples Repository](https://github.com/suites-dev/examples)** - Complete working examples for all testing patterns - diff --git a/docs/get-started/installation.mdx b/docs/get-started/installation.mdx deleted file mode 100644 index 80291b8..0000000 --- a/docs/get-started/installation.mdx +++ /dev/null @@ -1,298 +0,0 @@ ---- -sidebar_position: 3 -title: "Install Suites for NestJS, Inversify, Jest, Vitest, and Sinon" -description: "Step-by-step installation of Suites for NestJS or Inversify with Jest, Vitest, or Sinon. Includes tsconfig setup for emitDecoratorMetadata and experimentalDecorators." -keywords: [install suites, nestjs unit testing setup, inversify testing, vitest nestjs, jest nestjs setup, emitDecoratorMetadata nestjs, nestjs tsconfig, experimentalDecorators] ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Installation - -Get Suites up and running in your project. - -## Prerequisites - -- Node 18 or higher -- TypeScript project with decorators enabled -- A framework implementing IoC ([NestJS](https://nestjs.com/), [InversifyJS](https://inversify.io/)), or plain -TypeScript classes (manually) -- A testing library ([Jest](https://jestjs.io/), [Vitest](https://vitest.dev/), or [Sinon](https://sinonjs.org/)) - -:::tip Coming from Automock? -`@automock/jest` and `@automock/sinon` are deprecated and replaced by Suites. The -[migration guide](/docs/migration-guides/from-automock/) covers the rename and the -API differences; most projects are a dependency swap and a find-and-replace. -::: - -## Installation - -Install the core package: - -```bash -npm install -D @suites/unit -``` - -Install the adapters for your framework and testing library: - -<Tabs groupId="test-runner"> - <TabItem value="jest" label="Jest" default> - <Tabs groupId="package-manager"> - <TabItem value="npm" label="npm" default> - ```bash - # Install NestJS dependencies - npm install @nestjs/common @nestjs/core reflect-metadata - - # Install Suites core and adapters - npm install --save-dev @suites/unit @suites/di.nestjs @suites/doubles.jest - - # Install TypeScript and Jest - npm install --save-dev ts-jest @types/jest jest typescript - ``` - </TabItem> - <TabItem value="pnpm" label="pnpm"> - ```bash - # Install NestJS dependencies - pnpm add @nestjs/common @nestjs/core reflect-metadata - - # Install Suites core and adapters - pnpm add -D @suites/unit @suites/di.nestjs @suites/doubles.jest - - # Install TypeScript and Jest - pnpm add -D ts-jest @types/jest jest typescript - ``` - </TabItem> - <TabItem value="yarn" label="yarn"> - ```bash - # Install NestJS dependencies - yarn add @nestjs/common @nestjs/core reflect-metadata - - # Install Suites core and adapters - yarn add -D @suites/unit @suites/di.nestjs @suites/doubles.jest - - # Install TypeScript and Jest - yarn add -D ts-jest @types/jest jest typescript - ``` - </TabItem> - </Tabs> - </TabItem> - <TabItem value="vitest" label="Vitest"> - <Tabs groupId="package-manager"> - <TabItem value="npm" label="npm" default> - ```bash - # Install NestJS dependencies - npm install @nestjs/common @nestjs/core reflect-metadata - - # Install Suites core and adapters - npm install --save-dev @suites/unit @suites/di.nestjs @suites/doubles.vitest - - # Install TypeScript and Vitest - npm install --save-dev vitest unplugin-swc @swc/core typescript - ``` - </TabItem> - <TabItem value="pnpm" label="pnpm"> - ```bash - # Install NestJS dependencies - pnpm add @nestjs/common @nestjs/core reflect-metadata - - # Install Suites core and adapters - pnpm add -D @suites/unit @suites/di.nestjs @suites/doubles.vitest - - # Install TypeScript and Vitest - pnpm add -D vitest unplugin-swc @swc/core typescript - ``` - </TabItem> - <TabItem value="yarn" label="yarn"> - ```bash - # Install NestJS dependencies - yarn add @nestjs/common @nestjs/core reflect-metadata - - # Install Suites core and adapters - yarn add -D @suites/unit @suites/di.nestjs @suites/doubles.vitest - - # Install TypeScript and Vitest - yarn add -D vitest unplugin-swc @swc/core typescript - ``` - </TabItem> - </Tabs> - </TabItem> - <TabItem value="sinon" label="Sinon"> - <Tabs groupId="package-manager"> - <TabItem value="npm" label="npm" default> - ```bash - # Install NestJS dependencies - npm install @nestjs/common @nestjs/core reflect-metadata - - # Install Suites core and adapters - npm install --save-dev @suites/unit @suites/di.nestjs @suites/doubles.sinon - - # Install TypeScript and Sinon - npm install --save-dev sinon @types/sinon typescript - ``` - </TabItem> - <TabItem value="pnpm" label="pnpm"> - ```bash - # Install NestJS dependencies - pnpm add @nestjs/common @nestjs/core reflect-metadata - - # Install Suites core and adapters - pnpm add -D @suites/unit @suites/di.nestjs @suites/doubles.sinon - - # Install TypeScript and Sinon - pnpm add -D sinon @types/sinon typescript - ``` - </TabItem> - <TabItem value="yarn" label="yarn"> - ```bash - # Install NestJS dependencies - yarn add @nestjs/common @nestjs/core reflect-metadata - - # Install Suites core and adapters - yarn add -D @suites/unit @suites/di.nestjs @suites/doubles.sinon - - # Install TypeScript and Sinon - yarn add -D sinon @types/sinon typescript - ``` - </TabItem> - </Tabs> - </TabItem> -</Tabs> - -This installs three packages: -- `@suites/unit` - Core TestBed API -- `@suites/di.nestjs` - NestJS adapter for reading DI metadata -- `@suites/doubles.<runner>` - the adapter for the test runner selected above, which creates type-safe mocks - -**Suites will automatically detect the installed adapters and configure itself accordingly.** - -:::info Why reflect-metadata? -`reflect-metadata` is required as a runtime dependency (not dev dependency) because TypeScript's decorator metadata is only emitted at compile time. -Learn more in the [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/decorators.html#metadata). -::: - -## Supported Libraries (Adapters) - -### Framework Adapters: - -- `@suites/di.nestjs` - [NestJS](https://nestjs.com/) dependency injection -- `@suites/di.inversify` - [InversifyJS](https://inversify.io/) dependency injection - -### Available Mocking Libraries: - -- `@suites/doubles.jest` -- `@suites/doubles.sinon` -- `@suites/doubles.vitest` - -After installation, no additional configuration for the test runner is needed. - -## Setting Up `tsconfig.json` - -To use Suites, enable the [`emitDecoratorMetadata`](https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata) and [`experimentalDecorators`](https://www.typescriptlang.org/tsconfig#experimentalDecorators) options in the `tsconfig.json` file. -This configuration is necessary for Suites to reflect class dependencies and constructor metadata. - -```json title="tsconfig.json" -{ - "compilerOptions": { - "emitDecoratorMetadata": true, - "experimentalDecorators": true - } -} -``` - -:::note NodeNext / ESM projects -As of `3.1.0`, projects configured with `moduleResolution: NodeNext` or `"type": "module"` work with Suites out of the box. No additional configuration is required beyond the standard setup shown above. -::: - -## Type Reference Configuration - -To ensure type safety and maintain a clean API, Suites requires setting up type references for your mocking library. This approach enables importing all utilities from `@suites/unit` while maintaining proper type information. - -### Setting Up Type References - -1. **Create a `global.d.ts` file** in the project root directory (or the location specified as `rootDir` in the `tsconfig.json`). - -2. **Add reference types** for the mocking library and dependency injection framework: - -```typescript title="global.d.ts" -// Mocking library (choose one) -/// <reference types='@suites/doubles.jest/unit' /> -/// <reference types='@suites/di.nestjs/types' /> -// OR -/// <reference types='@suites/doubles.sinon/unit' /> -/// <reference types='@suites/di.inversify/types' /> -// OR -/// <reference types='@suites/doubles.vitest/unit' /> -/// <reference types='@suites/di.nestjs/types' /> -``` - -**Example for Jest + NestJS:** -```typescript title='global.d.ts' -/// <reference types='@suites/doubles.jest/unit' /> -/// <reference types='@suites/di.nestjs/types' /> -``` - -This configuration ensures the project correctly recognizes the types provided by Suites (like the `Mocked<T>` type) without needing to import them from library-specific modules. - -## Monorepo Support - -Suites is fully compatible with monorepo setups, accommodating projects that use different testing frameworks. - -### Recommended Setups - -When using Suites in a monorepo, consider the following setups: - -**Using the same mocking and dependency injection framework across all workspaces** \ -Install the corresponding adapter under the root directory of your monorepo. -Suites will automatically detect the adapter and configure itself accordingly. - -**Using different frameworks across workspaces** \ -Install the corresponding adapter in each workspace separately. Configure the package manager's hoisting settings -to enable Suites to detect the adapter in each workspace. - -:::note -Some package managers may have limitations with dependency hoisting that can affect using different DI or mocking libraries -across workspaces. Refer to the package manager's documentation for specific guidance on dependency hoisting. -::: - -## Vitest: decorator metadata setup - -When integrating Suites with Vitest, additional configuration is required. Vitest typically uses [`esbuild`](https://esbuild.github.io/) for -TypeScript interpretation, which does not support `emitDecoratorMetadata` - a feature extensively used by Suites for -reflecting class dependencies. To overcome this, switch to using [`@swc/core`](https://swc.rs/docs/usage/core) via a -plugin, which supports `emitDecoratorMetadata`. For detailed guidance, see the [Vitest documentation](https://vitest.dev/). - -Here is an example of how to configure Suites with Vitest: - -First, install the `unplugin-swc` and `@swc/core` packages and add it to the `vitest.config.ts` file as a plugin. -This will enable `@swc/core` to interpret TypeScript files and support `emitDecoratorMetadata`. - -```bash -$ npm install --save-dev unplugin-swc @swc/core -``` - -```typescript title='vitest.config.ts' -import swc from 'unplugin-swc'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { globals: true, root: './' }, - plugins: [ - swc.vite({ - module: { type: 'es6' }, - jsc: { transform: { decoratorMetadata: true } }, - }), - ], -}); -``` - -:::note Vitest 4.x -Vitest 4.x uses Oxc by default instead of esbuild. The `esbuild: false` option is a no-op in Vitest 4.x and produces a warning; remove it from any existing config. The configuration above works correctly with both Vitest 3.x and 4.x. Vitest 4.x is supported as of `3.1.0`. -::: - -<div class="next-steps-section"> -## What's Next? - - Check out the [Quick Start](/docs/get-started/quickstart/) guide to write the first test - - Explore the [Guides](/docs/guides/) section for testing patterns - - Browse [working examples](https://github.com/suites-dev/examples) for real-world code -</div> \ No newline at end of file diff --git a/docs/get-started/quickstart.md b/docs/get-started/quickstart.md deleted file mode 100644 index 70a2bae..0000000 --- a/docs/get-started/quickstart.md +++ /dev/null @@ -1,280 +0,0 @@ ---- -sidebar_position: 4 -title: "Suites Quick Start: Write Your First Unit Test in 5 Minutes" -description: "Write your first Suites unit test in 5 minutes. Walkthrough for NestJS and Inversify with Jest or Vitest." -keywords: [suites quickstart, nestjs unit test example, inversify unit test, first nestjs test, vitest nestjs example, typescript unit test tutorial] ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Quick Start Guide - -Write the first Suites test in 5 minutes. No manual mocks, no dependency injection configuration, no boilerplate. - -**What this guide covers:** - * Solitary tests with automatic mocking - * Sociable tests with real dependencies - * Zero-config testing - -:::info -Complete source code for this example (and more) is available in the [Suites Examples repository](https://github.com/suites-dev/examples). -::: - -## Prerequisites - -Before starting, ensure the [Installation guide](/docs/get-started/installation/) has been completed to set up the project with Suites. - -- Basic knowledge of TypeScript and unit testing -- Familiarity with dependency injection patterns -- Familiarity with unit testing basic concepts - -## Step 1: Creating the Application Code - -This guide creates example `UserService` and `UserRepository` classes to test. NestJS decorators are used here, but -**Suites works with any DI framework** (InversifyJS, etc.) or even plain TypeScript classes with constructor injection: - -```typescript title='src/types.ts' -export interface User { - id: number; - name: string; - email: string; -} -``` - -### Create a Repository Class - -```typescript title='src/user.repository.ts' -import { Injectable } from '@nestjs/common'; -import { User } from './types'; - -@Injectable() -export class UserRepository { - async getUserById(id: number): Promise<User> { - // In a real app, this would fetch from a database - return { id, name: 'John Doe', email: 'john@example.com' }; - } -} -``` - -### Create a Service Class with a Dependency - -```typescript title='src/user.service.ts' -import { Injectable } from '@nestjs/common'; -import { UserRepository } from './user.repository'; -import { User } from './types'; - -@Injectable() -export class UserService { - constructor(private userRepository: UserRepository) {} - - async getUserName(id: number): Promise<string> { - const user = await this.userRepository.getUserById(id); - return user.name; - } -} -``` - -## Step 2: Writing the First Test with Suites - -This example writes a test for a `UserService` that depends on a `UserRepository`: - -```typescript title="src/user.service.spec.ts" -import { type Mocked, TestBed } from '@suites/unit'; -import { UserService } from './user.service'; -import { UserRepository } from './user.repository'; - -describe('User Service Unit Spec', () => { - let userService: UserService; - let userRepository: Mocked<UserRepository>; - - beforeAll(async () => { - const { unit, unitRef } = await TestBed.solitary(UserService).compile(); - - userService = unit; - userRepository = unitRef.get(UserRepository); - }); - - it('should return the user name and call repository', async () => { - userRepository.getUserById.mockResolvedValue({ - id: 1, - name: 'John Doe', - email: 'john@doe.com', - }); - - const result = await userService.getUserName(1); - - expect(userRepository.getUserById).toHaveBeenCalledWith(1); - expect(result).toBe('John Doe'); - }); -}); -``` - -### Running the Test - -Run tests with: - -```bash -npm test -``` - -You should see output like: - -``` - PASS src/user.service.spec.ts - User Service Unit Spec - ✓ should return the user name and call repository (5ms) - -Tests: 1 passed, 1 total -Time: 0.5s -``` - -### What Just Happened? - -When `TestBed.solitary(UserService).compile()` is called, Suites automatically: - -1. **Analyzed** `UserService`'s constructor and discovered its `UserRepository` dependency -2. **Created** a type-safe mock of `UserRepository` with all methods automatically stubbed -3. **Injected** the mock into `UserService` without manual wiring -4. **Provided** type-safe access to both the service (`unit`) and its mocks (`unitRef`) - -**What was not required:** Manually creating mocks, configuring dependency injection, or writing test setup boilerplate. - -:::tip Key Terminology - -- **Solitary Test**: A test where all dependencies are automatically mocked (complete isolation) -- **Mock**: A fake object with stubbed methods (e.g., `Mocked<UserRepository>`) -- **Stub**: A fake method that returns predefined values (e.g., `mockResolvedValue(...)`) -::: - -## Step 3: Testing with Real Dependencies (Sociable Mode) - -Test how components work together using real implementations with **sociable tests**: - -```typescript title='src/notification.service.spec.ts' -import { TestBed } from '@suites/unit'; -import { NotificationService } from './notification.service'; -import { TemplateService } from './template.service'; -import { EmailClient } from './email.client'; - -describe('Notification Service Sociable Spec', () => { - it('should use real TemplateService but mock EmailClient', async () => { - const { unit, unitRef } = await TestBed.sociable(NotificationService) - .expose(TemplateService) // Use real TemplateService - .compile(); - - const emailClient = unitRef.get(EmailClient); - emailClient.send.mockResolvedValue({ success: true }); - - await unit.sendWelcomeEmail('user@example.com'); - - // TemplateService.format() runs with REAL logic - // EmailClient.send() is mocked - expect(emailClient.send).toHaveBeenCalledWith( - expect.objectContaining({ - to: 'user@example.com', - subject: 'Welcome!', - }) - ); - }); -}); -``` - -:::tip -Sociable tests verify that components integrate correctly while keeping tests fast by mocking external boundaries (databases, HTTP clients, etc.). - -Learn more in the [Sociable Unit Testing guide](/docs/guides/sociable/). -::: - -## How It Works: No Modules, No Bootstrapping - -This guide uses NestJS's `@Injectable()` decorators but never creates a NestJS module or calls `Test.createTestingModule()`. This is by design, **and NestJS is just one example**. Suites works the same way with InversifyJS, TSyringe, or any TypeScript class with constructor injection. - -### The Traditional Testing Problem - -Traditional testing approaches require extensive boilerplate and manual setup: - -<Tabs> -<TabItem value="manual" label="Manual" default> - -```typescript -// Manual: Define mock classes for every dependency -class MockUserRepository { - findById = jest.fn(); - save = jest.fn(); - delete = jest.fn(); - // ... more methods -} - -class MockEmailService { - send = jest.fn(); - validate = jest.fn(); - // ... more methods -} - -const mockRepo = new MockUserRepository(); -const mockEmail = new MockEmailService(); -const userService = new UserService(mockRepo, mockEmail); -// No DI benefits, pure manual wiring -``` - -</TabItem> -<TabItem value="nestjs" label="NestJS"> - -```typescript -// NestJS: Complex module setup -const module = await Test.createTestingModule({ - imports: [DatabaseModule, ConfigModule, LoggerModule], - providers: [ - UserService, - { provide: UserRepository, useValue: mockRepo }, - { provide: EmailService, useValue: mockEmail }, - // ... wire every dependency - ] -}).compile(); -// Wait 2-5 seconds for module initialization... -``` - -</TabItem> -<TabItem value="inversify" label="InversifyJS"> - -```typescript -// InversifyJS: Container configuration -const container = new Container(); -container.bind(TYPES.UserRepository).toConstantValue(mockRepo); -container.bind(TYPES.EmailService).toConstantValue(mockEmail); -container.bind(TYPES.UserService).toSelf(); -// Manual container setup for every test -``` - -</TabItem> -</Tabs> - -### The Suites Solution: Virtual Test Container - -Suites bypasses framework initialization entirely: - -```typescript -// Suites: Direct to testing, no ceremony -const { unit, unitRef } = await TestBed.solitary(UserService).compile(); -// Ready in 50-100ms -``` - -**How it works:** -1. **Metadata extraction** - Reads TypeScript decorator metadata directly -2. **Selective mocking** - Creates only the dependencies your class needs -3. **Direct instantiation** - Constructs your class with mocks injected -4. **Zero overhead** - No modules, no framework, no waiting - -**The result:** Tests run 20-100x faster while remaining type-safe and maintainable. - -**Deep dive:** [Virtual Test Container Guide](/docs/guides/virtual-test-container/) - -## What's Next? - -- [Unit Testing Fundamentals](/docs/guides/fundamentals/) - Learn about the core principles of unit testing -- [Test Doubles](/docs/guides/test-doubles/) - Understand how to work with mocks, stubs, and other test doubles -- [Solitary Unit Testing](/docs/guides/solitary/) - Dive deeper into testing components in complete isolation -- [Sociable Unit Testing](/docs/guides/sociable/) - Explore testing with real implementations of select dependencies - -Need help? Join the [Suites community on GitHub Discussions](https://github.com/suites-dev/suites/discussions) or report issues on our [GitHub repository](https://github.com/suites-dev/suites). diff --git a/docs/get-started/why-suites.md b/docs/get-started/why-suites.md deleted file mode 100644 index 1fd911d..0000000 --- a/docs/get-started/why-suites.md +++ /dev/null @@ -1,282 +0,0 @@ ---- -sidebar_position: 2 -title: Why Suites? -description: Eliminate boilerplate in dependency injection testing. Suites provides type-safe test doubles and automatic mocking for TypeScript applications following the IoC principle. ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Why Suites? - -Unit testing TypeScript applications with complex dependencies is **expensive and slow**. Whether using dependency injection containers, plain constructor injection, or functional composition, manual mocking creates brittle tests, cryptic errors, and mountains of boilerplate that bury test intent. Teams waste weeks debugging broken test doubles, onboarding junior developers, and maintaining inconsistent unit testing patterns across projects. - -## The Core Problem - -Manual mock setup in IoC and DI patterns creates three critical failures: -* **Silent breakage** (untyped mocks) -* **Cryptic runtime errors** (missing implementations) -* **Excessive boilerplate** (many lines per test) - -## The Quick Win - -Here's what Suites removes: - -<Tabs> -<TabItem value="manual" label="Manual" default> - -```typescript showLineNumbers -// ❌ Before: many lines defining mock classes, no DI benefits -class MockInventoryService { - checkStock = jest.fn(); - reserveStock = jest.fn(); - releaseStock = jest.fn(); - // ... 5 more methods -} - -class MockPaymentService { - authorize = jest.fn(); - capture = jest.fn(); - refund = jest.fn(); - // ... 5 more methods -} - -const mockInventory = new MockInventoryService(); -const mockPayment = new MockPaymentService(); -const orderService = new OrderService(mockInventory, mockPayment, /* ... */); -``` - -</TabItem> -<TabItem value="nestjs" label="NestJS"> - -```typescript -// ❌ Before: many lines of manual setup, no type safety -let mockInventory: any = { checkStock: jest.fn(), /* ... */ }; -let mockPayment: any = { authorize: jest.fn(), /* ... */ }; - -const module = await Test.createTestingModule({ - providers: [ - OrderService, - { provide: InventoryService, useValue: mockInventory }, - { provide: PaymentService, useValue: mockPayment }, - // ... wire up every dependency manually - ], -}).compile(); - -orderService = module.get<OrderService>(OrderService); -``` - -</TabItem> -<TabItem value="inversify" label="InversifyJS"> - -```typescript -// ❌ Before: many lines of manual setup, no type safety -let container = new Container(); -let mockInventory: any = { checkStock: jest.fn(), /* ... */ }; -let mockPayment: any = { authorize: jest.fn(), /* ... */ }; - -container.bind<InventoryService>(TYPES.InventoryService) - .toConstantValue(mockInventory); -container.bind<PaymentService>(TYPES.PaymentService) - .toConstantValue(mockPayment); -container.bind<OrderService>(TYPES.OrderService).toSelf(); - -orderService = container.get<OrderService>(TYPES.OrderService); -``` - -</TabItem> -</Tabs> - - -**After: One line, fully typed (works for all frameworks):** - -```typescript -const { unit, unitRef } = await TestBed.solitary(OrderService).compile(); - -orderService = unit; -inventoryService = unitRef.get(InventoryService); // Fully typed! -``` - -## The Problems with Manual Dependency Injection Testing - -### 1. Manual Mocks Break Silently During Refactors - -Backend teams waste hours writing untyped mocks. TypeScript errors on incomplete mocks force developers to cast to 'any', which breaks silently when dependencies change or methods are missing. - -<details> -<summary>Show example</summary> - -```typescript -// Developers cast to 'any' to bypass TypeScript errors -const mockRepo = { - findById: jest.fn().mockResolvedValue({ id: 1, name: 'John' }) -} as any; // or unknown - ⚠️ silences all type checks - -const service = new UserService(mockRepo); - -// Later, UserRepository adds 'findByEmail' method... -// TypeScript won't catch this - mock is typed as 'any' -await service.getUserByEmail('test@example.com'); -// Runtime error: "TypeError: userRepository.findByEmail is not a function" -``` -</details> - -### 2. Excessive Boilerplate Obscures Test Intent and Confuses LLMs - -Each test requires 30+ lines of mock setup. This cognitive load slows development, confuses junior developers, and makes AI assistants struggle to generate correct tests. When tests fail with cryptic errors, LLMs cannot self-correct, burning tokens without progress. - -**LLM token cost and context engineering:** Manual mocking forces AI agents to hold 40+ lines of boilerplate per test in context. With Suites' single canonical pattern (`TestBed.solitary()`), one example teaches the entire API, which reducing context consumption by 95% while improving accuracy. - -<details> -<summary>Show example</summary> - -```typescript -describe('Order Service Unit Spec', () => { - beforeEach(() => { - mockInventory = { checkStock: jest.fn(), /* 7 more methods */ } as any; - mockPayment = { authorize: jest.fn(), /* 8 more methods */ } as unknown as jest.Mocked<...>; - mockNotification = { sendEmail: jest.fn(), /* 5 more */ } as any; - // ... repeat for 5 more services - orderService = new OrderService(mockInventory, mockPayment, mockNotification); - }); - - // Where's the test? Buried in boilerplate. -}); -``` -</details> - -### 3. Inconsistent Patterns Across Teams - -Different teams use different testing approaches (manual instantiation, mock classes, container registration patterns). Developers switching projects must relearn patterns every time. No standard emerges, even within the same organization. - -<details> -<summary>Show example</summary> - -```typescript -// Team A: manual object mocks -const mockDb = { query: jest.fn() }; - -// Team B: mock class instances -class MockDatabase { query = jest.fn(); } - -// Team C: container registration -const container = new Container(); -container.register(Database, { useValue: { query: jest.fn() } }); - -// Developers switching teams must learn new patterns each time -``` - -</details> - -## How Suites Solves It - -Suites provides a **declarative API** that removes manual mocking entirely. A single call creates a fully-typed, -isolated unit testing environment with type-safe test doubles. No boilerplate, no cryptic errors, no silent failures. - -:::tip Current Testing Options - -**Using dependency injection frameworks?** -`TestBed` provides automatic mock injection and reference tracking - the best testing experience. - -**Not using dependency injection?** -Use `mock()` and `stub()` for type-safe mocking. Works with any TypeScript code, but requires manual dependency wiring -and reference tracking. See [mock() and stub() utilities](/docs/api-reference/mock/). -::: - -```typescript title="order.service.spec.ts" {1,9,11-13} -import { TestBed, type Mocked } from '@suites/unit'; - -describe('Order Service Sociable Unit Spec', () => { - let orderService: OrderService; - let inventoryService: Mocked<InventoryService>; - let paymentService: Mocked<PaymentService>; - - beforeAll(async () => { - const { unit, unitRef } = await TestBed.solitary(OrderService).compile(); - - orderService = unit; - inventoryService = unitRef.get(InventoryService); - paymentService = unitRef.get(PaymentService); - // All dependencies auto-mocked and fully typed! - }); - - test('should place order', async () => { - // Test intent is immediately clear - inventoryService.checkStock.mockResolvedValue(true); - await orderService.placeOrder({ items: ['item1'] }); - - // TypeScript validates at compile time: - // ✅ inventoryService.checkStock exists - // ❌ inventoryService.nonExistentMethod is a compile error - }); -}); -``` - -**What Suites provides:** - -- **Type-Safe Refactoring**: Compile-time validation with mocks bound to implementations prevent silent test failures -and let teams change constructors or add dependencies without breaking tests. -- **Zero Boilerplate**: One line replaces dozens of lines of manual mock setup. -- **Standardized Testing Across Teams**: A single canonical API enforces consistent testing patterns across projects and frameworks, so every team shares the same approach. -- **AI-Friendly**: Single canonical pattern reduces token count for LLMs. One `TestBed.solitary()` example teaches the entire API, unlike 40+ lines of manual setup requiring exhaustive edge-case documentation. - -### Framework Support - -Suites works seamlessly with frameworks implementing IoC using the same clean API. - -The same applies to test runners. Jest, Vitest and Sinon each have an adapter, and -the `TestBed` API is identical across all three, so the examples below and -everywhere else in these docs work whichever runner a project uses. Switching -runners means changing one dependency, not rewriting tests. - -<Tabs> -<TabItem value="nestjs" label="NestJS" default> - -```typescript -import { Injectable } from '@nestjs/common'; - -@Injectable() -class PaymentService { - constructor(private gateway: PaymentGateway) {} -} - -const { unit, unitRef } = await TestBed.solitary(PaymentService).compile(); -``` -</TabItem> -<TabItem value="inversify" label="InversifyJS"> - -```typescript -import { injectable } from 'inversify'; - -@injectable() -class PaymentService { - constructor(private gateway: PaymentGateway) {} -} - -const { unit, unitRef } = await TestBed.solitary(PaymentService).compile(); -``` -</TabItem> -<TabItem value="injection-js"> - -```typescript -import { injectable } from 'injection-js'; - -@Injectable() -class PaymentService { - constructor(private gateway: PaymentGateway) {} -} - -const { unit, unitRef } = await TestBed.solitary(PaymentService).compile(); -``` -</TabItem> -</Tabs> - -Same API, same patterns across frameworks. All utilities are exported from a single entry point: `@suites/unit` - -## In Summary - -Suites replaces thousands of lines of brittle, manual test setup with a single, declarative call. Backend teams gain -confidence in their unit testing, improve refactor safety with solitary and sociable unit tests, and enable both -developers and AI tools to write and maintain reliable test suites. - -**Ready to try it?** Check out the [Quick Start](/docs/get-started/quickstart/) guide to write the first test in 5 minutes. diff --git a/docs/guides/index.md b/docs/guides/index.md deleted file mode 100644 index 2f31cf6..0000000 --- a/docs/guides/index.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -sidebar_position: 2 -title: Guides -description: Practical guides for testing with Suites -toc_min_heading_level: 3 ---- - -# Testing Guides - -Practical guides for writing solitary and sociable unit tests with Suites. Learn how to test components in isolation, verify real interactions between classes, and control external dependencies. - - -<div class="in-this-section"> - -### In This Section - -- [**Fundamentals of Unit Testing**](/docs/guides/fundamentals/) - Core principles and best practices -- [**Solitary Unit Tests**](/docs/guides/solitary/) - Testing components in complete isolation -- [**Sociable Unit Tests**](/docs/guides/sociable/) - Testing with real dependencies -- [**Test Doubles**](/docs/guides/test-doubles/) - Mocks, stubs, and spies -- [**Virtual Test Container**](/docs/guides/virtual-test-container/) - How Suites creates test environments - -</div> - -:::tip -Start with [Fundamentals](/docs/guides/fundamentals/), then [Solitary](/docs/guides/solitary/) and [Sociable](/docs/guides/sociable/) testing. -::: - -:::tip Working Examples -Browse the [Suites Examples repository](https://github.com/suites-dev/examples) for complete, runnable examples of solitary, sociable, and real-world testing patterns. -::: - - diff --git a/docs/guides/sociable.md b/docs/guides/sociable.md deleted file mode 100644 index 706deab..0000000 --- a/docs/guides/sociable.md +++ /dev/null @@ -1,329 +0,0 @@ ---- -sidebar_position: 4 -title: Testing Components Together (Sociable) -description: Testing real component interactions with Suites ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Testing Components Together (Sociable Testing) - -> **What this covers:** Testing real component (class) interactions while controlling external dependencies \ -> **Time to read:** ~15 minutes \ -> **Prerequisites:** [Unit Testing Fundamentals](/docs/guides/fundamentals/), [Solitary Unit Tests](/docs/guides/solitary/), [Test Doubles](/docs/guides/test-doubles/) \ -> **Best for:** Verifying components (classes) work together correctly, catching integration bugs while maintaining test speed - -Sociable unit tests verify how components interact with their real dependencies while keeping external I/O operations under control. This approach identifies bugs in how business logic components work together - bugs that solitary tests miss. - -## Sociable Tests Are Unit Tests - -Sociable tests are **unit tests**, not integration tests. They keep external I/O (databases, APIs, file systems) mocked to stay fast and side-effect-free. The integration bugs they catch are issues in how business logic components work with each other - not issues with real external systems. \ -For the distinction, see [Unit Testing Fundamentals: Quick Reference](/docs/guides/fundamentals/#quick-reference). - -## Overview - -This tutorial covers: -1. Setting up a sociable test with real dependencies -2. Handling external dependencies and I/O operations -3. Managing multiple dependencies using `.expose()` -4. Choosing when to configure mocks (before vs after compilation) -5. Using a decision framework to choose what to mock - -## Step 1: Set Up the First Sociable Test - -This example tests a `UserService` that depends on an `EmailValidator`. - -### 1.1 Create the Services - -```typescript title="src/services/email-validator.ts" -@Injectable() -export class EmailValidator { - isValid(email: string): boolean { - return email.includes('@') && email.includes('.'); - } -} -``` - -```typescript title="src/services/user.service.ts" -@Injectable() -export class UserService { - constructor(private validator: EmailValidator) {} - - createUser(email: string) { - if (!this.validator.isValid(email)) { - throw new Error('Invalid email'); - } - return { email }; - } -} -``` - -### 1.2 Write the Sociable Test - -The `.expose()` method tells Suites to use the real implementation instead of a mock. - -```typescript title="src/services/user.service.spec.ts" -import { TestBed } from '@suites/unit'; -import { UserService, EmailValidator } from './services'; - -describe('UserService', () => { - let userService: UserService; - - beforeAll(async () => { - const { unit } = await TestBed.sociable(UserService) - .expose(EmailValidator) // Use real EmailValidator - .compile(); - - userService = unit; - }); - - it('validates email using real logic', () => { - const result = userService.createUser('test@example.com'); - expect(result.email).toBe('test@example.com'); - }); - - it('rejects invalid email', () => { - expect(() => userService.createUser('invalid')) - .toThrow('Invalid email'); - }); -}); -``` -The real `EmailValidator` runs its actual validation logic. If the validator has a bug, this test will detect it. - -## Step 2: Handle External Dependencies - -Most services interact with external systems like databases. This example extends the previous one. - -### 2.1 Add Database Dependency - -```typescript title="src/services/user.service.ts" -@Injectable() -export class UserService { - constructor( - private validator: EmailValidator, - @Inject('DATABASE') private db: DatabaseClient // Token injection - ) {} - - async createUser(email: string) { - if (!this.validator.isValid(email)) { - throw new Error('Invalid email'); - } - return this.db.users.save({ email }); - } -} -``` - -### 2.2 Test with Mocked I/O - -Dependencies injected using tokens are automatically mocked. - -```typescript title="src/services/user.service.spec.ts" -import { TestBed, Mocked } from '@suites/unit'; - -describe('UserService', () => { - let userService: UserService; - let database: Mocked<DatabaseClient>; - - beforeAll(async () => { - const { unit, unitRef } = await TestBed.sociable(UserService) - .expose(EmailValidator) - .compile(); - - userService = unit; - database = unitRef.get<DatabaseClient>('DATABASE'); - }); - - it('saves valid user', async () => { - database.users.save.mockResolvedValue({ id: 1, email: 'test@example.com' }); - - const result = await userService.createUser('test@example.com'); - - expect(result.id).toBe(1); - expect(database.users.save).toHaveBeenCalledWith({ email: 'test@example.com' }); - }); -}); -``` - -Third-party packages use `@Inject('TOKEN')` because they're not `@Injectable()` classes. Dependencies injected this way are **always mocked** automatically. - -## Step 3: Manage Multiple Dependencies - -As services grow, you need to manage more dependencies. Here's how to handle them: - -### 3.1 Service with Many Dependencies - -```typescript title="src/services/order.service.ts" -@Injectable() -export class OrderService { - constructor( - private pricingService: PricingService, - private taxCalculator: TaxCalculator, - private inventoryChecker: InventoryChecker, - private discountEngine: DiscountEngine, - @Inject('DATABASE') private db: DatabaseClient, - @Inject('EMAIL_SERVICE') private email: EmailClient - ) {} - - async processOrder(items: OrderItem[], region: string) { - const subtotal = this.pricingService.calculateTotal(items); - const tax = this.taxCalculator.calculateTax(subtotal, region); - const total = subtotal + tax; - - const order = await this.db.orders.create({ items, subtotal, tax, total }); - await this.email.send({ to: order.customerEmail, template: 'order-confirmation' }); - - return order; - } -} -``` - -### 3.2 Using `.expose()` - -List each dependency that should use its real implementation. - -```typescript title="src/services/order.service.spec.ts" -describe('OrderService', () => { - let orderService: OrderService; - let database: Mocked<DatabaseClient>; - - beforeAll(async () => { - const { unit, unitRef } = await TestBed.sociable(OrderService) - .expose(PricingService) // Use real implementation - .expose(TaxCalculator) // Use real implementation - .expose(InventoryChecker) // Use real implementation - .expose(DiscountEngine) // Use real implementation - .compile(); - - orderService = unit; - database = unitRef.get<DatabaseClient>('DATABASE'); - }); - - it('processes order with real calculations', async () => { - const items = [{ price: 10, quantity: 6 }]; - database.orders.create.mockResolvedValue({ id: 123 }); - - await orderService.processOrder(items, 'US'); - - // All exposed services run real code - expect(database.orders.create).toHaveBeenCalledWith({ - items, - subtotal: 54, // Real discount calculation - tax: 4.32, // Real tax calculation - total: 58.32 - }); - }); -}); -``` - -:::tip Understanding the Control Boundary -Suites can only control **explicit dependencies** (passed through constructors). It cannot control implicit dependencies (direct imports). For more, see [Test Doubles](/docs/guides/test-doubles/). -::: - -## Step 4: Choose When to Configure Mocks - -You can configure mocks before compilation (for consistent values) or after compilation (for test-specific scenarios). - -<Tabs> -<TabItem value="pre" label="Before Compilation" default> - -**When to use:** Set default values that all tests in the suite need. - -**Best for:** Shared test data, reducing repetitive setup code - -```typescript title="src/services/user.service.spec.ts" -beforeAll(async () => { - const { unit, unitRef } = await TestBed.sociable(UserService) - .expose(EmailValidator) - .mock('DATABASE') - .final({ - users: { - save: async () => ({ id: 42, email: 'test@example.com' }) - } - }) - .compile(); - - userService = unit; - // Database always returns the same value -}); - -it('creates user', async () => { - const result = await userService.createUser('test@example.com'); - expect(result.id).toBe(42); // Always 42 -}); -``` -</TabItem> -<TabItem value="post" label="After Compilation"> - -**When to use:** Configure different behaviors for individual tests. - -**Best for:** Test-specific scenarios, error cases, varying responses - -```typescript title="src/services/user.service.spec.ts" -beforeAll(async () => { - const { unit, unitRef } = await TestBed.sociable(UserService) - .expose(EmailValidator) - .compile(); - - userService = unit; - database = unitRef.get<DatabaseClient>('DATABASE'); -}); - -it('handles save error', async () => { - // Configure for this specific test - database.users.save.mockRejectedValue(new Error('Connection failed')); - - await expect(userService.createUser('test@example.com')) - .rejects.toThrow('Connection failed'); -}); - -it('handles successful save', async () => { - // Different configuration for different test - database.users.save.mockResolvedValue({ id: 99 }); - - const result = await userService.createUser('test@example.com'); - expect(result.id).toBe(99); -}); -``` -</TabItem> -</Tabs> - -## Decision Framework - -Use this flowchart to decide how to handle each dependency: - -```mermaid -flowchart LR - A[Dependency] --> B{External I/O?} - B -->|Yes| C[@Inject TOKEN<br/>Auto-mocked] - B -->|No| D{Business logic<br/>to test?} - D -->|Yes| E[.expose Class<br/>Uses real code] - D -->|No| F[Leave mocked<br/>default behavior] -``` - -**Rules:** -- **External I/O** (databases, APIs, file systems) → Use token injection → Auto-mocked -- **Business logic** to test → Use `.expose()` → Runs real code -- **Everything else** → Leave as default → Auto-mocked - -## Summary - -Sociable tests work alongside solitary tests to provide comprehensive coverage: - -- **Solitary tests:** Verify individual class behavior in isolation -- **Sociable tests:** Verify components work together correctly - -### Takeaways - -- Sociable tests verify how components interact using real implementations for business logic -- External systems (I/O) are always mocked using token injection (`@Inject('TOKEN')`) -- Use `.expose()` to specify which dependencies should use real implementations -- Configure mocks before compilation for consistent values, or after for test-specific scenarios -- Follow the decision tree: External I/O → token injection, business logic → expose, everything else stays mocked - -## Next Steps - -After understanding sociable testing, explore these resources: - -- **[Test Doubles](/docs/guides/test-doubles/)**: Core concepts of mocking and stubbing -- **[Suites Examples Repository](https://github.com/suites-dev/examples)**: Working examples of sociable testing patterns diff --git a/docs/guides/solitary.md b/docs/guides/solitary.md deleted file mode 100644 index 6411dcd..0000000 --- a/docs/guides/solitary.md +++ /dev/null @@ -1,176 +0,0 @@ ---- -sidebar_position: 3 -title: Testing in Isolation (Solitary) -description: Testing in isolation with Suites ---- - -# Testing Unit In Isolation (Solitary Testing) - -> **What this covers:** Writing unit tests that isolate a single component (class) using automatic mocking \ -> **Time to read:** ~8 minutes \ -> **Prerequisites:** [Unit Testing Fundamentals](/docs/guides/fundamentals/), [Test Doubles](/docs/guides/test-doubles/) \ -> **Best for:** Testing component logic in complete isolation, controlling all inputs for predictable results - -Solitary unit tests evaluate a single unit of work in complete isolation from its external dependencies. These tests use -mocks (collections of stubs) to replace dependencies, enabling testing of component behavior without influence from its -collaborators. - -**Solitary tests are ideal when:** - -- Testing the logic and behavior of a single component (class) -- Controlling all inputs to ensure predictable test results -- Developing new components and ensuring they work correctly in isolation - -In contrast to [sociable unit tests](/docs/guides/sociable/), where certain dependencies are real implementations, solitary tests replace all dependencies with mocks to ensure true isolation. - -## Terminology - -Before diving in, it is important to understand these core concepts: - - -* **Stub:** An individual method that returns predefined values. Configure stubs using `.mockResolvedValue()` or - `.mockReturnValue()` (like `jest.fn()`). -* **Mock:** A replacement for a dependency class where all methods are stubs - simply a collection of stubs -* **`Mocked<T>:`** A type representing a class where all methods are stubs ([see `Mocked<T>` type](/docs/api-reference/types/#mockedt)). - -For a deeper understanding of test doubles, see [Test Doubles](/docs/guides/test-doubles/) after completing this guide. - -## Overview - -This tutorial walks you through: -1. Setting up a class to test with dependencies -2. Writing your first solitary test with automatic mocking -3. Configuring mock behavior for different test scenarios - -## Step 1: Set Up the Class Under Test - -This example tests `UserService` which depends on `UserApi` and `Database`: - -```typescript title="user.service.ts" -@Injectable() -class UserService { - constructor( - private readonly userApi: UserApi, // < This considered dependency - private readonly database: Database // < This considered dependency - ) {} - - async generateRandomUser(): Promise<number> { - const randomUser = await this.userApi.getRandom(); - return this.database.saveUser(randomUser); - } -} -``` - -## Step 2: Write the First Solitary Test - -This example demonstrates writing a solitary unit test for the `UserService` class: - -```typescript title="user.service.spec.ts" {1,13,15,18-19} showLineNumbers -import { TestBed, type Mocked } from '@suites/unit'; -import { UserService, UserApi, Database } from './services'; - -describe('UserService Tests', () => { - let userService: UserService; // Class under test - - // Declare the mock instances - let userApi: Mocked<UserApi>; - let database: Mocked<Database>; - - beforeAll(async () => { - // Create the test environment with automatic mocking - const { unit, unitRef } = await TestBed.solitary(UserService).compile(); - - userService = unit; - - // Retrieve the mock instances - userApi = unitRef.get(UserApi); - database = unitRef.get(Database); - }); - - it('should generate a random user and save to the database', async () => { - // Configure the stubbed methods to return predefined values - userApi.getRandom.mockResolvedValue({ id: 1, name: 'John' } as User); - database.saveUser.mockResolvedValue(42); - - // Test the outcome, not the implementation details - const result = await userService.generateRandomUser(); - - // Verify the result is what we expect (the return value from database.saveUser) - expect(result).toBe(42); - }); -}); -``` - -### What Just Happened? - -The test setup uses `TestBed.solitary()` to create an isolated testing environment: - -1. **TestBed analyzes the class** - Reads `UserService` constructor to find `UserApi` and `Database` dependencies -2. **Automatic mocks are created** - Generates mock instances of `UserApi` and `Database` with all methods as stubs -3. **Dependencies are injected** - Wires the mocks into `UserService` constructor automatically -4. **Type-safe access** - Use `unitRef.get()` to retrieve mocks with full TypeScript support - -No manual mock creation needed. `TestBed` handles dependency discovery, mock generation, and wiring automatically. - -### Automatic Mocking of Dependencies - -When using `TestBed.solitary()`, all dependencies are automatically mocked. Each method becomes a stub with no predefined responses. Configure stub responses in tests as needed. - -```typescript -// These stubs start with no return values -userApi.getRandom // Returns undefined by default -database.saveUser // Returns undefined by default - -// Configure them in your tests -userApi.getRandom.mockResolvedValue({ id: 1, name: 'John' }); -database.saveUser.mockResolvedValue(42); -``` - -:::info How This Works -See [Virtual Test Container](/docs/guides/virtual-test-container/) for how the virtual test container works under the hood. -::: - -## Step 3: Configure Mock Behavior - -Configure mocks before or after calling `.compile()` depending on test needs: - -```typescript -// Configure after compile (most common) -beforeAll(async () => { - const { unit, unitRef } = await TestBed.solitary(UserService).compile(); - userService = unit; - userApi = unitRef.get(UserApi); -}); - -it('test case', () => { - userApi.getRandom.mockResolvedValue({ id: 1, name: 'John' }); - // ... test logic -}); -``` - -:::info -For advanced configuration options, see [Mock Configuration](/docs/api-reference/mock-configuration/). -::: - -## Summary - -- **Solitary tests** evaluate a single unit in complete isolation from dependencies -- **Automatic mocking** with `TestBed.solitary()` eliminates manual mock setup -- **Virtual container** creates only the mocks needed, not the full framework -- **Type safety** with `Mocked<T>` ensures stub methods are properly typed -- **Configure stubs** in your tests to control dependency behavior -- **Use solitary tests** when you need complete control over inputs and want predictable results - -## Next Steps - -For testing how components work together, explore different approaches: - -- **[Sociable Unit Tests](/docs/guides/sociable/)**: Test multiple classes together with controlled collaboration -- **[Test Doubles](/docs/guides/test-doubles/)**: Understand mocks, stubs, and spies in depth - -## Additional Resources - -- **[Suites Examples Repository](https://github.com/suites-dev/examples)**: Working examples of solitary testing patterns -- **[API Reference - TestBed.solitary](/docs/api-reference/testbed-solitary/)**: Complete API documentation for TestBed.solitary() -- **[API Reference - Mock Configuration](/docs/api-reference/mock-configuration/)**: Advanced mock configuration options -- **[API Reference - Types](/docs/api-reference/types/)**: TypeScript type definitions including `Mocked<T>` diff --git a/docs/guides/test-doubles.md b/docs/guides/test-doubles.md deleted file mode 100644 index 35f4a2c..0000000 --- a/docs/guides/test-doubles.md +++ /dev/null @@ -1,220 +0,0 @@ ---- -sidebar_position: 2 -title: "TypeScript Test Doubles: Auto-Generated Mocks, Stubs, Spies & Fakes" -description: "Type-safe test doubles for TypeScript unit tests. Auto-generate mocks, stubs, spies, and fakes for NestJS, Inversify, Jest, and Vitest using Suites." -keywords: [test doubles, mocks, stubs, spies, fakes, mocks vs stubs, typescript testing, unit testing, mock generation typescript, auto mocks, nestjs testing, vitest mocks, jest mocks] ---- - -# Test Doubles: Mocks, Stubs, Spies, and Fakes - -> **What this covers:** What test doubles are, the four main types, when to use each, and how Suites generates them -> automatically for TypeScript backends. \ -> **Time to read:** ~10 minutes \ -> **Prerequisites:** Familiarity with unit testing concepts. New to Suites? Start with -> the [Quickstart](/docs/get-started/quickstart/). \ -> **Best for:** Understanding the theory behind test doubles after hands-on experience with solitary testing - -A **test double** is a stand-in object you use in a unit test in place of a real dependency (a database, an HTTP client, -a third-party SDK), so the test stays fast, deterministic, and isolated from the outside world. - -The term comes from [Gerard Meszaros's _xUnit Test Patterns_](http://xunitpatterns.com/Test%20Double.html) and was -popularised by [Martin Fowler's "Mocks Aren't Stubs"](https://martinfowler.com/articles/mocksArentStubs.html). Both -works distinguish four common kinds of test double: - -| Type | What it does | Typical use | -|----------|-----------------------------------------------------------------------|-----------------------------------------------------------------------------------------| -| **Stub** | Returns a hard-coded value when called. No verification. | Make a dependency return what your test needs. | -| **Mock** | Records calls so the test can assert *how* the dependency was used. | Verify the system-under-test called `repo.save()` exactly once with the right argument. | -| **Spy** | Wraps a real object, records calls, but lets the real method execute. | Observe behaviour without changing it. | -| **Fake** | A working but simplified implementation (e.g. in-memory database). | When the real dependency is too heavy but a stub would lose too much realism. | - -The distinction that catches most people out: a **stub** answers *queries*, a **mock** verifies *commands*. Fowler calls -this _state verification_ vs _behaviour verification_. If you're unsure which you need, you almost always want a stub. - -## Why test doubles matter - -Without them, a "unit" test ends up exercising your entire dependency tree: the database, the network, the file system. -The result is slow, flaky, and tells you very little about the unit you actually changed. Test doubles let you isolate -one piece of code, make its environment deterministic, and assert on it precisely. - -## Test doubles in Suites - -Writing test doubles by hand is repetitive: for every dependency, you build a fake class, stub each method, wire it into -your DI container. Suites does this automatically. Given a class with constructor-injected dependencies, Suites -generates a fully-mocked version of every dependency, no manual `jest.fn()` calls, no hand-rolled fakes. - -```typescript -import { TestBed, Mocked } from '@suites/unit'; - -const { unit, unitRef } = await TestBed.solitary(UserService).compile(); -const repo: Mocked<UserRepository> = unitRef.get(UserRepository); - -repo.findById.mockResolvedValue(testUser); // stub a return value -await unit.activateUser('123'); -expect(repo.save).toHaveBeenCalledWith(testUser); // verify a call -``` - -That's it - `UserRepository` was generated as a mock with every method pre-stubbed. No setup file, no manual mock -factory. - -> **Try it:** Set up Suites and write your first test in under five minutes. See -> the [Quickstart](/docs/get-started/quickstart/). - -The rest of this guide explains how Suites uses the terms _mock_ and _stub_, which dependencies it can and can't -replace, and how to choose between state and behaviour verification in practice. - -## How Suites Uses "Mocks" and "Stubs" - -Suites uses these terms in a specific way: - -**Mock:** A fake replacement of an entire class. When Suites creates a mock, all the methods in that class become stubs. - -```typescript -const repo: Mocked<UserRepository> = unitRef.get(UserRepository); -// repo is a mock - a fake version of UserRepository -// All its methods (findById, save, etc.) are now stubs -``` - -**Stub:** A single method that returns a value you specify. - -```typescript -repo.findById.mockResolvedValue(testUser); -// findById is now a stub that returns testUser -``` - -**Simple rule:** When you see `Mocked<UserRepository>`, it means you have a mock (fake class) where all methods are -stubs (fake methods). - -## Dependencies: What Can Be Replaced? - -Suites can only create test doubles for **explicit dependencies** - dependencies that are passed in through the -constructor or method parameters. It cannot replace **implicit dependencies** - things that are imported directly at the -top of a file. - -**Explicit dependencies** (Suites can replace these): - -```typescript -@Injectable() -export class UserService { - constructor( - private database: Database, // ✅ Suites can create a mock - private emailService: EmailService // ✅ Suites can create a mock - ) {} -} -``` - -**Implicit dependencies** (Suites cannot replace these): - -```typescript -import { sendEmail } from './email-utils'; // ❌ Direct import - -@Injectable() -export class UserService { - createUser(data: UserData) { - sendEmail(data.email); // ❌ Always runs the real code - } -} -``` - -When there are implicit dependencies, they always execute the real code during tests because Suites has no way to intercept them. - -:::tip -Not all implicit dependencies are a problem. Simple utility functions like `Math.floor()` or `formatDate()` are fine. -But operations that touch the network, database, or file system should be explicit dependencies so you can replace them in tests. -::: - -## Two Ways to Verify Tests - -You can verify that your code works correctly in two different ways: - -**State Verification:** -Check what the final result is, not how you got there. - -```typescript -repo.findById.mockResolvedValue(testUser); -const result = await service.getUserName(1); -expect(result).toBe('John Doe'); // Did we get the right answer? -``` - -**Behavior Verification:** -Check that the right methods were called with the right arguments. - -```typescript -await service.createUser(userData); -expect(repo.save).toHaveBeenCalledWith(userData); // Was save() called correctly? -``` - -Both approaches work well with Suites. Choose the one that makes sense for what you're testing. - -:::note Common Pitfalls -Using `spyOn()` or manual mocks (`jest.mock()`/`vitest.mock()`) frequently, usually means the code has too many -implicit dependencies. Consider refactoring to use explicit dependencies instead (pass dependencies in through the constructor) -::: - -## Using Mocks in Suites - -Suites automatically creates mocks for all dependencies. - -```typescript -const { unit, unitRef } = await TestBed.solitary(UserService).compile(); -const repo = unitRef.get(UserRepository); // Automatically a mock! - -// Now configure what the stub methods should return -repo.findById.mockResolvedValue(testUser); -``` - -For more details on configuration options, see: - -- [Mock Configuration](/docs/api-reference/mock-configuration/) - Using `.mock().final()` and `.mock().impl()` -- [Types](/docs/api-reference/types/) - Details about the `Mocked<T>` type -- [mock() function](/docs/api-reference/mock/) - Creating mocks manually - -## Testing with Real Implementations - -Sometimes you want to test how multiple components work together using real code, not mocks. Use sociable tests for -this: - -```typescript -const { unit } = await TestBed.sociable(UserService) - .expose(UserRepository) // Use the real UserRepository - .compile(); - -// Now both UserService and UserRepository use real implementations -const result = await unit.createUser(userData); -expect(result.id).toBeDefined(); // Real database logic executed -``` - -See [Sociable Unit Tests](/docs/guides/sociable/) for more information. - -## Summary - -### Quick Reference - -| Term | What It Means | Example | -|-------------------------|-----------------------------------------------------------|-----------------------------------------| -| **Test Double** | Any fake replacement for a real dependency | Any mock, stub, spy, fake, or dummy | -| **Mock** | A fake version of an entire class (all methods are stubs) | `Mocked<UserRepository>` | -| **Stub** | A fake method that returns a predetermined value | `repo.findById.mockResolvedValue(user)` | -| **Spy** | A stub that also records how it was called | `jest.spyOn()` - not recommended | -| **Explicit Dependency** | Passed in through constructor or parameters | `constructor(private db: Database)` | -| **Implicit Dependency** | Imported directly at the top of the file | `import { sendEmail } from './utils'` | - -### Takeaways - -- **Test doubles** are fake versions of dependencies that enable control during tests -- **In Suites**: A "mock" is a fake class where all methods are "stubs" -- **Two verification styles**: Check the final result (state) or check method calls (behavior) -- **Automatic mocking**: Suites creates mocks for all dependencies automatically with full type safety -- **Explicit dependencies**: Only dependencies passed through constructors or parameters can be mocked - -## Next Steps - -Learn more about using test doubles in practice: - -- **[Solitary Unit Tests](/docs/guides/solitary/)**: Test components in complete isolation using automatic mocking -- **[Sociable Unit Tests](/docs/guides/sociable/)**: Test multiple components together with real implementations - -## Additional Resources - -- **[Martin Fowler - Mocks Aren't Stubs](https://martinfowler.com/articles/mocksArentStubs.html)**: Classic article - explaining different testing approaches \ No newline at end of file diff --git a/docs/migration-guides/index.md b/docs/migration-guides/index.md deleted file mode 100644 index 183b08b..0000000 --- a/docs/migration-guides/index.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -sidebar_position: 5 -title: Migration Guides -description: Guides for migrating to Suites or upgrading versions ---- - -# Migration Guides - -Guides for migrating to Suites from other testing frameworks or upgrading between Suites versions. - -## Available Guides - -### [From Automock](/docs/migration-guides/from-automock/) -Migrate from Automock to Suites with step-by-step instructions and API mapping. - -## Future Guides - -- **v3 to v4 Migration** - Coming soon -- **From Manual Mocking** - Coming soon diff --git a/docusaurus.config.ts b/docusaurus.config.ts deleted file mode 100644 index 6a55bd3..0000000 --- a/docusaurus.config.ts +++ /dev/null @@ -1,175 +0,0 @@ -import algoliaConfig from "./config/algolia-config"; -import type { Config } from "@docusaurus/types"; -import { draculaThemeConfig } from "./config/prisma-theme.config"; - -// Netlify sets CONTEXT to "production" | "deploy-preview" | "branch-deploy". -// It is unset locally and in CI, so analytics never fires outside a real -// production deploy. SUITES_FORCE_GTAG is an escape hatch for reproducing a -// tagging bug locally without editing this file. -const isProductionDeploy = - process.env.CONTEXT === "production" || process.env.SUITES_FORCE_GTAG === "1"; - -console.log( - `[docusaurus] CONTEXT=${process.env.CONTEXT ?? "(unset)"} gtag=${isProductionDeploy ? "enabled" : "disabled"}`, -); - -const config: Config = { - title: "Suites Documentation", - tagline: "A unit testing framework for TypeScript backends working with inversion of control and dependency injection", - organizationName: "suites-dev", - url: "https://suites.dev", - baseUrl: "/", - trailingSlash: true, - onBrokenLinks: "throw", - onBrokenMarkdownLinks: "throw", - favicon: "img/favicon.png", - projectName: "suites", - markdown: { - mermaid: true, - }, - themes: ['@docusaurus/theme-mermaid'], - headTags: [ - { - tagName: "link", - attributes: { - rel: "preconnect", - href: "https://fonts.googleapis.com", - }, - }, - ], - i18n: { - defaultLocale: "en", - locales: ["en"], - }, - presets: [ - [ - "@docusaurus/preset-classic", - { - docs: { - path: "docs", - routeBasePath: "docs", - sidebarPath: require.resolve("./config/docs-sidebars.js"), - editUrl: "https://github.com/suites-dev/docs.suites.dev/edit/master/", - remarkPlugins: [ - [require("@docusaurus/remark-plugin-npm2yarn"), { sync: true }], - ], - }, - theme: { - customCss: require.resolve("./src/css/custom.css"), - }, - blog: false, - ...(isProductionDeploy - ? { - gtag: { - trackingID: "G-G7FJBNFPJJ", - anonymizeIP: true, - }, - } - : {}), - sitemap: { - changefreq: "weekly", - priority: 0.5, - filename: "sitemap.xml", - ignorePatterns: ["/404", "/404/", "/search", "/search/"], - }, - }, - ], - ], - themeConfig: { - metadata: [ - { - name: "keywords", - content: - "auto mocking, unit testing framework, jest, sinon, vitest, dependency injection, inversion of control, nestjs, inversifyjs, unit testing, test doubles, typescript testing, mock generation, test automation, solitary testing, sociable testing", - }, - { name: "author", content: "Suites" }, - { name: "robots", content: "index, follow" }, - { name: "googlebot", content: "index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" }, - { - name: "description", - content: "A unit testing framework for TypeScript backends working with inversion of control and dependency injection", - }, - { property: "og:title", content: "Suites - A Modern Unit Testing Framework" }, - { - property: "og:description", - content: "A unit testing framework for TypeScript backends working with inversion of control and dependency injection", - }, - { - property: "og:image", - content: "https://suites.dev/img/banner.png", - }, - { property: "og:image:width", content: "1200" }, - { property: "og:image:height", content: "630" }, - { property: "og:image:alt", content: "Suites - A Modern Unit Testing Framework" }, - { property: "og:url", content: "https://suites.dev" }, - { property: "og:type", content: "website" }, - { property: "og:site_name", content: "Suites" }, - { property: "og:locale", content: "en_US" }, - // Twitter Card - { name: "twitter:card", content: "summary_large_image" }, - { name: "twitter:title", content: "Suites - A Modern Unit Testing Framework" }, - { - name: "twitter:description", - content: "A unit testing framework for TypeScript backends working with inversion of control and dependency injection", - }, - { name: "twitter:image", content: "https://suites.dev/img/banner.png" }, - { name: "twitter:image:alt", content: "A unit testing framework for TypeScript backends working with inversion of control and dependency injection" }, - // Additional SEO - { name: "theme-color", content: "#1a1a1a" }, - { name: "application-name", content: "Suites" }, - ], - colorMode: { - disableSwitch: true, - defaultMode: "dark", - }, - // The crawler only matches /docs/**, so no record carries the - // docusaurus_tag:default value that contextual search sends from the - // homepage and other src/pages routes, and search there returns nothing. - // One docs version and one language means contextual filtering buys us - // nothing, so every route queries the same index. - algolia: { ...algoliaConfig, contextualSearch: false }, - navbar: { - title: "Suites", - logo: { - alt: "Suites Logo", - src: "img/logo.png", - }, - items: [ - { - to: "/docs/get-started/", - position: "left", - label: "Get Started", - }, - { - to: "/docs/guides/", - position: "left", - label: "Guides", - }, - { - href: "https://github.com/suites-dev/examples", - position: "left", - label: "Examples", - }, - { - to: "/docs/api-reference/", - position: "left", - label: "API Reference", - }, - { - href: "https://buymeacoffee.com/omermoradd", - position: "right", - label: "Sponsor", - className: "header-sponsor-link", - }, - { - href: "https://github.com/suites-dev/suites", - position: "right", - className: "header-github-link", - }, - ], - }, - prism: draculaThemeConfig, - }, -}; - -export default config; diff --git a/ec.config.mjs b/ec.config.mjs new file mode 100644 index 0000000..33151ab --- /dev/null +++ b/ec.config.mjs @@ -0,0 +1,21 @@ +import { defineEcConfig, ExpressiveCodeTheme } from "astro-expressive-code"; +import darkTheme from "./src/styles/suites-code-theme.json" with { type: "json" }; +import lightTheme from "./src/styles/suites-code-light-theme.json" with { type: "json" }; + +const suitesCodeTheme = ExpressiveCodeTheme.fromJSONString( + JSON.stringify(darkTheme), +); + +const suitesLightCodeTheme = ExpressiveCodeTheme.fromJSONString( + JSON.stringify(lightTheme), +); + +export default defineEcConfig({ + themes: [suitesCodeTheme, suitesLightCodeTheme], + themeCssSelector: (theme) => `[data-theme="${theme.type}"]`, + useDarkModeMediaQuery: false, + styleOverrides: { + borderColor: "var(--border-strong)", + frames: { frameBoxShadowCssValue: "none" }, + }, +}); diff --git a/leodemoura-github-io-blog-2026-2-28-when-ai-writes-the-world.md b/leodemoura-github-io-blog-2026-2-28-when-ai-writes-the-world.md new file mode 100644 index 0000000..2fa9e6c --- /dev/null +++ b/leodemoura-github-io-blog-2026-2-28-when-ai-writes-the-world.md @@ -0,0 +1,337 @@ +![Leonardo de Moura](https://leodemoura.github.io/static/images/leo.jpg) + +[Leonardo de Moura](https://leodemoura.github.io/) + +# When AI Writes the World's Software, Who Verifies It? + +2026-02-28 + +## AI Is Rewriting the World's Software + +[Code Metal recently raised $125 million](https://www.wired.com/story/vibe-coding-startup-code-metal-raises-series-b-fundraising/) +to rewrite defense industry code using +AI. [Google](https://www.itpro.com/technology/artificial-intelligence/sundar-pichai-says-more-than-25-percent-of-googles-code-is-now-generated-by-ai-and-its-a-big-hint-at-the-future-of-software-development) +and [Microsoft](https://www.cnbc.com/2025/04/29/satya-nadella-says-as-much-as-30percent-of-microsoft-code-is-written-by-ai.html) +both report that 25–30% of their new code is +AI-generated. [AWS used AI to modernize 40 million lines of COBOL for Toyota.](https://aws.amazon.com/blogs/migration-and-modernization/accelerate-mainframe-modernization-with-aws-transform-a-comprehensive-refactor-approach/) +Microsoft's CTO predicts +that [95% of all code will be AI-generated by 2030](https://www.techspot.com/news/107411-microsoft-cto-predicts-ai-generate-95-percent-code.html). +The rewriting of the world's software is not coming. It is underway. + +Anthropic recently built a [100,000-line C compiler](https://www.anthropic.com/engineering/building-c-compiler) using +parallel AI agents in two weeks, for under $20,000. It boots Linux and compiles SQLite, PostgreSQL, Redis, and Lua. AI +can now produce large-scale software at astonishing speed. But can it prove the compiler correct? Not yet. + +**No one is formally verifying the result.** + +[Andrej Karpathy](https://x.com/karpathy/status/1886192184808149383) described the pattern: "I 'Accept All' always, I +don't read the diffs anymore." When AI code is good enough most of the time, humans stop reviewing +carefully. [Nearly half of AI-generated code fails basic security tests, and newer, larger models do not generate significantly more secure code than their predecessors.](https://www.veracode.com/blog/genai-code-security-report/) +The errors are there. The reviewers are not. Even Karpathy does not trust it: he +later [outlined a cautious workflow](https://x.com/karpathy/status/1915581920022585597) for "code \[he\] actually +care\[s\] about," and when +he [built his own serious project](https://futurism.com/artificial-intelligence/inventor-vibe-coding-doesnt-work), he +hand-coded it. + +Consider what happens at scale. A single bug in OpenSSL — [Heartbleed](https://en.wikipedia.org/wiki/Heartbleed) — +exposed the private communications of millions of users, survived two years of code review, +and [cost the industry hundreds of millions of dollars](https://www.csoonline.com/article/562859/the-heartbleed-bug-how-a-flaw-in-openssl-caused-a-security-crisis.html) +to remediate. That was one bug, introduced by one human, in one library. AI is now generating code at a thousand times +the speed, across every layer of the software stack, and the defenses we relied on (code review, testing, manual +inspection) are the same ones that missed Heartbleed for two years. + +The Harvard Business Review recently documented what it calls "workslop": AI-generated work that looks polished but +requires someone downstream to fix. When that work is a memo, it is annoying. When it is a cryptographic library, it is +catastrophic. As AI accelerates the pace of software production, the verification gap does not shrink. It widens. +Engineers stop understanding what their systems do. AI outsources not just the writing but the thinking. + +The threat extends beyond accidental errors. When AI writes the software, the attack surface shifts: an adversary who +can poison training data or compromise the model's API can inject subtle vulnerabilities into every system that AI +touches. These are not hypothetical risks. Supply chain attacks are already among the most damaging in cybersecurity, +and AI-generated code creates a new supply chain at a scale that did not previously exist. Traditional code review +cannot reliably detect deliberately subtle vulnerabilities, and a determined adversary can study the test suite and +plant bugs specifically designed to evade it. A formal specification is the defense: it defines what "correct" means +independently of the AI that produced the code. When something breaks, you know exactly which assumption failed, and so +does the auditor. + +Poor software quality +already [costs the U.S. economy $2.41 trillion per year](https://news.synopsys.com/2022-12-06-Software-Quality-Issues-in-the-U-S-Cost-an-Estimated-2-41-Trillion-in-2022), +according to a 2022 study by the Consortium for Information & Software Quality. That number was calculated before AI +began writing a quarter or more of new code at leading companies. Chris Lattner, the creator of LLVM and +Clang, [put it bluntly](https://www.modular.com/blog/the-claude-c-compiler-what-it-reveals-about-the-future-of-software): +AI amplifies both good and bad structure. Bad code at AI speed becomes "incomprehensible nightmares." As AI generates an +increasing share of the world's critical infrastructure (financial systems, medical devices, defense, transportation), +unverified code becomes a systemic risk, not just a quality problem. + +Most critical software today works, maintained by talented engineers using testing, code review, and hard-won +experience. The problem is not that everything is broken. It is that AI is changing the scale and speed of software +production faster than our ability to verify it. What works at human pace may not survive AI pace. + +**It will get worse, and it will get worse fast, unless verification scales with generation.** + +## Why Mathematical Proof + +Testing and proof are complementary. Testing, including property-based testing and fuzzing, is powerful: it catches bugs +quickly, cheaply, and often in surprising ways. But testing provides confidence. Proof provides a **guarantee**. The +difference matters, and it is hard to quantify how high the confidence from testing actually is. Software can be +accompanied by proofs of its correctness, proofs that a machine checks mechanically, with no room for error. When AI +makes proof cheap, it becomes the stronger path: one proof covers every possible input, every edge case, every +interleaving. A verified cryptographic library is not better engineering. It is a mathematical guarantee. + +Consider an example. An AI rewrites a TLS library. The code passes every test. But the specification requires +constant-time execution: no branch may depend on secret key material, no memory access pattern may leak information. The +AI's implementation contains a subtle conditional that varies with key bits, a timing side-channel invisible to testing, +invisible to code review. A formal proof of constant-time behavior catches it instantly. Without the proof, that +vulnerability ships to production. Proving such low-level properties requires verification at the right level of +abstraction, which is why the platform must support specialized sublanguages for reasoning about timing, memory layout, +and other hardware-level concerns. + +The Claude C Compiler illustrates the other side: it optimizes for passing tests, not for correctness. It hard-codes +values to satisfy the test suite. It will not generalize. Property-based testing would likely catch this particular +case, but the general problem remains: for any fixed testing strategy, a sufficiently adversarial system can overfit to +it. A proof cannot be gamed. It covers all inputs by construction. + +The friction of writing code manually used to force careful design. AI removes that friction, including the beneficial +friction. The answer is not to slow AI down. It is to replace human friction with mathematical friction: let AI move +fast, but make it prove its work. The new friction is productive: writing specifications and models, defining precisely +what "correct" means, designing before generating. + +The obstacle has always been cost. Writing proofs by hand was too expensive to apply broadly. AI changes the economics. +Proof is becoming practical at scale. + +## Accelerating Progress + +Most people think of verification as a cost, a tax on development, justified only for safety-critical systems. That +framing is outdated. When AI can generate verified software as easily as unverified software, verification is no longer +a cost. It is a catalyst. + +The value is not in the verification workforce. It is in what verified delivery enables. Consider a company delivering +ML kernels for new hardware. Today, months go to testing and qualification. When AI writes the kernel and proves it +correct in one pass, that timeline collapses to hours. A provably correct hardware design delivered in weeks rather than +the year it currently takes changes the economics of an entire industry. + +Verification, testing, and specification have always been the bottleneck, not implementation. Good engineers know what +they want to build. They just cannot afford to prove it correct. If that cost drops to near zero, every domain where +correctness matters accelerates. Aerospace, automotive, and medical device certification currently takes years of +qualification effort. Cloud providers invest similar effort qualifying security-critical services and cryptographic +implementations. Verified code generation could collapse that timeline to weeks. Hardware verification, where a single +bug can cost hundreds of millions of dollars, benefits equally. + +This acceleration requires specifications: precise descriptions of what the software must do. As AI takes over +implementation, specification becomes the core engineering discipline. Writing a specification forces clear thinking +about what a system must do, what invariants it must maintain, what can go wrong. This is where the real engineering +work has always lived. Implementation just used to be louder. + +Writing specifications is not always easy, but it is easier than writing the optimized implementation. And a powerful +shortcut exists: an inefficient program that is obviously correct can serve as its own specification. User and AI +co-write a simple model, AI writes an efficient version, and proves the two equivalent. The hard part shifts from +implementation to design. That is the right kind of hard. + +This is not limited to critical systems. Any non-trivial engineering project where bugs are expensive (which is most of +them) accelerates when correctness is cheap. + +## What the Verification Platform Needs + +What would a verification platform for the AI era require? + +A small, trusted kernel: a few thousand lines of code that check every step of every proof mechanically. Everything +else (the AI, the automation, the human guidance) is outside the trust boundary. Independent reimplementations of that +kernel, in different +languages ( [Lean](https://github.com/digama0/lean4lean), [Rust](https://github.com/ammkrn/nanoda_lib)), serve as +cross-checks. You do not need to trust a complex AI or solver; you verify the proof independently with a kernel small +enough to audit completely. The verification layer must be separate from the AI that generates the code. In a world +where AI writes critical software, the verifier is the last line of defense. If the same vendor provides both the AI and +the verification, there is a conflict of interest. Independent verification is not a philosophical preference. It is a +security architecture requirement. The platform must be open source and controlled by no single vendor. + +The platform must be both a programming language and a theorem prover, with code and proofs in one system, with no +translation gap. It needs a rich and extensible tactic framework that gives AI structured, incremental feedback: here is +the current goal, here are the hypotheses available, here is what changed after each step. AI must control the proof +search, not delegate to a black box. + +The alternative, push-button solvers that return a binary pass or fail with no intermediate state, gives AI nothing to +learn from and no way to guide the search. Worse, proofs that rely on heuristic solvers often break when the solver +updates or when developers make small changes to how they write their specifications, even when the changes are +logically equivalent. You cannot build a reliable AI pipeline on a foundation that is not reproducible. (I discuss this +in detail in a recent [Stanford talk](https://leodemoura.github.io/stanford2026/).) + +It needs the largest possible library of formalized knowledge to build on. Verifying software is mathematics: the same +reasoning that proves a theorem in abstract algebra proves that a cryptographic library correctly implements its +specification. A platform that serves mathematicians and engineers is not a compromise. It is the recognition that +rigorous reasoning is one discipline, whether applied to prime numbers or protocol correctness. + +And it needs deep extensibility. Users and AI must be able to write extensions that access the system's internals, +building custom tools, automation, and domain-specific reasoning engines. This is already happening: AI agents build +their own proof strategies on top of the platform. The platform adapts to its users, not the other way around. + +**The AI community has already made its choice. +** [AlphaProof](https://deepmind.google/blog/ai-solves-imo-problems-at-silver-medal-level/) (Google DeepMind), +Aristotle ( [Harmonic](https://harmonic.fun/)), [SEED Prover](https://arxiv.org/abs/2507.23726) ( +ByteDance), [Axiom](https://axiommath.ai/), [Aleph](https://logicalintelligence.com/blog/aleph-solves-putnambench) ( +Logical Intelligence), and [Mistral AI](https://x.com/AlbertQJiang/status/1973506751782666536) all build +on [Lean](https://lean-lang.org/). Every major AI reasoning system that has achieved medal-level performance at the +International Mathematical Olympiad used Lean. No competing platform was used by any of them. The future is much larger +than today's early applications. + +Lean is backed by [Mathlib](https://github.com/leanprover-community/mathlib4), the largest coherent body of formalized +mathematics ever created: +over [200,000 formalized theorems and 750 contributors](https://leanprover-community.github.io/mathlib_stats.html). Five +Fields medalists engage with Lean. The same platform serves mathematicians formalizing theorems and engineers verifying +production systems. ACM SIGPLAN recognized this convergence with +its [2025 Programming Languages Software Award](https://www.sigplan.org/Awards/Software/): "Lean has become the de facto +choice for AI-based systems of mathematical reasoning." + +Enterprise teams already use Lean in production: AWS verified +its [Cedar](https://www.amazon.science/blog/how-we-built-cedar-with-automated-reasoning-and-differential-testing) +authorization policy engine, and Microsoft is using Lean to verify +its [SymCrypt](https://www.microsoft.com/en-us/research/blog/rewriting-symcrypt-in-rust-to-modernize-microsofts-cryptographic-library/) +cryptographic library. Over [8,000 GitHub repositories](https://reservoir.lean-lang.org/) contain Lean code. Over +200,000 users have installed the programming environment. More than 700 people are active in +the [Lean Zulip channel](https://leanprover.zulipchat.com/) every day. Research groups worldwide contribute to the +ecosystem. As Chris Lattner observed, manual rewrites and translation work are becoming AI-native tasks. AI will rewrite +the world's codebase. The platform it does so on matters enormously. + +Lean already produces performance comparable to Haskell and OCaml. When higher performance is essential, Lean models can +be translated into efficient imperative code embedded in Lean, with clean semantics and without C's undefined behavior. +We are actively working on closing the remaining gap for performance-critical code. The real comparison is not Lean +versus C. It is verified code versus unverified code. + +Lean is the result of [over twelve years of continuous development](https://lean-lang.org/fro/about/). We designed and +built every layer from scratch: the trusted kernel, the compiler, the language server, the IDE, the proof automation. +The team is 20 people. The community independently chose Lean: mathematicians, AI researchers, and enterprise engineers, +all building on the same platform. + +## A Sign of Things to Come + +At the Lean FRO, Kim Morrison, a Senior Research Software Engineer, recently ran an experiment that went well beyond our +expectations. An AI agent converted [zlib](https://github.com/kim-em/lean-zip), a widely used C compression library +embedded in countless systems, to Lean, with minimal human guidance. No special tooling was built. It was Claude, a +general-purpose AI, with no special training for theorem proving, out of the box. The workflow had four steps. First, +the AI produced a clean, readable Lean implementation of the zlib compression format, including the DEFLATE algorithm at +its core. Second, the Lean version passed the library's existing test suite, confirming behavioral equivalence. Third, +key properties were stated and proved, not as tests, but as mathematical theorems. The capstone theorem: + +``` +theorem zlib_decompressSingle_compress (data : ByteArray) (level : UInt8) + (hsize : data.size < 1024 * 1024 * 1024) : + ZlibDecode.decompressSingle (ZlibEncode.compress data level) = .ok data +``` + +This is a machine-checked proof that decompressing a compressed buffer always returns the original data, at every +compression level, for the full zlib format. The size precondition (`hsize`) is a proof engineering artifact: it bounds +the fuel the AI used during proof construction. A human engineer would likely eliminate it; the AI, working without +guidance, chose a bound that was sufficient to close the proof. As both AI capabilities and platform tooling improve, +artifacts like this will disappear. The raw DEFLATE and gzip framing roundtrips are proved as well. Fourth, an optimized +version is being developed and proved equivalent to the verified model. This work is ongoing. + +The entire proof was constructed with minimal human guidance and no specialized tooling. The result demonstrates that AI +can convert production software to a verified form today. This was not expected to be possible yet. + +zlib is a sequential algorithm with a clean RFC specification, simpler than a database engine or a distributed system. +The gap between this result and a verified software stack is real. But until recently, no one had demonstrated +AI-generated proofs about production software. The trajectory matters more than the starting point. Crucially, the zlib +proof was produced by a general-purpose AI with no specialized training for theorem proving. No custom model was needed. +This means the barrier to verified software is no longer AI capability. It is platform readiness. As general-purpose AI +improves, the bottleneck shifts entirely to the verification platform: how rich is the feedback it gives AI, how +powerful is the automation, how large is the library of prior knowledge to build on. + +The zlib workflow also reveals something testing structurally cannot do. When your tests and your code share the same +wrong assumptions, testing finds nothing. The act of proving forces every assumption to be explicit: a designer states a +property, the AI turns it into a precise formal statement, and then either constructs a proof or discovers that the +specification needs revision. Wrong assumptions surface as proof obligations that cannot be discharged. Specifications +become sharper through the attempt to prove them. + +The approach extends beyond sequential algorithms to distributed systems, where the hardest bugs live. Ilya Sergey's +group at NUS built [Veil](https://proofsandintuitions.net/2026/02/09/distributed-verification-veil/), a distributed +protocol verifier on top of Lean that combines model checking with full formal proof. When a property does not hold, +Veil generates concrete counterexamples: actual execution traces that demonstrate the failure, just as model checkers +do. When it does hold, Veil produces a full formal proof, not just the absence of counterexamples up to some bound. Veil +has verified protocols including Rabia, a randomized consensus protocol, proving agreement and validity for any number +of nodes. During verification, the team discovered an inconsistency in a prior formal verification of Rabia that had +gone undetected across two separate tools. For simpler protocols, Claude Code can infer most of the required auxiliary +invariants within minutes. + +The broader picture confirms the trajectory. AI systems using Lean +have [solved open mathematical problems](https://arxiv.org/html/2601.07421v1) that resisted decades of human effort. A +single mathematician working with an AI +agent [formalized the full Prime Number Theorem in three weeks](https://x.com/mathematics_inc/status/1998792071562604890): +25,000 lines of Lean, over 1,000 theorems. The previous formalization took over a year and dozens of contributors. +Fields Medalist Maryna Viazovska's proof that the E8 lattice is +the [optimal sphere packing in eight dimensions](https://www.linkedin.com/posts/hausdorff-center_fields-medalist-maryna-viazovska-epfl-activity-7432200595731763201-R7-t) +has been formally verified in Lean, with an AI agent completing the final steps. Google DeepMind's AlphaEvolve +discovered a new mathematical construction, and +AlphaProof [formalized the proof in Lean](https://www.nature.com/articles/s41586-025-09833-y), a complete AI pipeline +from discovery to verified proof. + +The productivity gap is widening: teams with the best tools are pulling further ahead while others stagnate. +Verification will be a decisive advantage. + +## The Verified Stack + +Where this leads is clear. Layer by layer, the critical software stack will be reconstructed with mathematical proofs +built in. The question is not whether this happens, but when. + +It is already beginning. The ecosystem is growing. Startups are training AI on proof data, building verification tools, +and growing talent. This is phase one: verifying existing code using Lean. Phase two is reconstruction: building the +stack in Lean from the ground up, with proofs built in from the start. Both phases happen on the same platform. Both +strengthen it. + +The target is the foundation of the modern software stack: cryptography, because everything else trusts it. Core +libraries (data structures, algorithms, compression) because they are the building blocks of all software. Storage +engines like SQLite, embedded in every device on earth. Parsers and protocol implementations (JSON, HTTP, DNS, +certificate validation) because every message passes through them. And compilers and runtimes, because they build +everything else. + +Consider SQLite, embedded in every smartphone, every browser, every major operating system. A verified SQLite carries +proofs that a crash during a write cannot corrupt the database, that concurrent readers never see partial transactions. +Testing tools like Jepsen and stateful property-based testing find many such bugs in practice, but they provide +confidence, not a guarantee. Mathematical proof covers every crash point and every interleaving. + +Each verified component is a permanent public good. Unlike proprietary software, a verified open-source library cannot +be degraded, cannot have its guarantees quietly revoked, and cannot be held hostage by a single company's business +decisions. The proofs are public. Anyone can audit them, build on them, or replace the implementation while preserving +the guarantees. This is infrastructure in the deepest sense: it raises the floor for everyone. When you build on this +stack, you inherit the proofs. Each layer is independent. The platform and methodology come first; then the work +parallelizes naturally. + +Once verified components are cheap, you compose them with confidence. Today, integration is where most bugs live: the +boundaries between components, the assumptions that do not quite match. Integration testing exists precisely because +component tests do not compose: passing unit tests on two components tells you nothing about whether they work together. +Verified interfaces are fundamentally different. When each component carries a proof against a shared specification, the +composition is guaranteed correct by construction. You can verify each piece independently and know the whole is sound. +This advantage scales superlinearly: the larger the system, the wider the gap between tested and verified. + +## The World This Creates + +The goal is a verified software stack: open source, freely available, mathematically guaranteed correct. Developers +building critical systems choose verified components the way they choose open-source libraries today, except these carry +proofs, not just tests. No single team builds this alone, and no single platform is guaranteed to be the right one +forever. What matters is that verified software becomes the norm. If something better than Lean emerges, the mission has +still succeeded. It is an open challenge for the entire community. + +The role of the engineer changes, but it does not shrink. Engineers spend more time writing specifications and models, +designing systems at a higher level of abstraction, defining precisely what systems must do, what invariants they must +maintain, what failures they must tolerate. The work becomes more creative, not less: designing before generating, +thinking before building. Productivity comes not from generating more code, but from generating code that is provably +correct on the first attempt. + +Specifications also raise a question that is not purely technical: who decides what "correct" means? A specification for +a medical device, a voting system, or an AI safety monitor encodes values, not just logic. Making specifications formal +and public does not resolve these questions, but it makes them explicit and auditable in a way that buried code never +can. AI itself can bridge the accessibility gap: the same systems that generate verified code can explain specifications +in plain language, making formal guarantees accessible beyond the experts who write them. + +The AI industry has a strategic stake in this outcome. An AI that generates provably correct code is qualitatively +different from one that merely generates plausible code. Verification transforms AI code generation from a productivity +tool into a trust infrastructure. + +AI is going to write a great deal of the world's software. It will advance mathematics, science, and engineering in ways +we cannot yet anticipate. The question is whether anyone can prove the results correct. + + --- + +Lean is open source and freely available at [lean-lang.org](https://lean-lang.org/). To learn Lean, start with +our [documentation](https://lean-lang.org/learn/). To join the community, visit +the [Lean Zulip](https://leanprover.zulipchat.com/). The [Lean Focused Research Organization](https://lean-fro.org/) +builds and maintains the platform. If you want to support this work, contact us +at [contact@lean-fro.org](mailto:contact@lean-fro.org). \ No newline at end of file diff --git a/loveable.css b/loveable.css new file mode 100644 index 0000000..6d1612d --- /dev/null +++ b/loveable.css @@ -0,0 +1,4649 @@ +/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */ +@layer properties { + @supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))) { + *, :before, :after, ::backdrop { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-translate-z: 0; + --tw-rotate-x: initial; + --tw-rotate-y: initial; + --tw-rotate-z: initial; + --tw-skew-x: initial; + --tw-skew-y: initial; + --tw-space-y-reverse: 0; + --tw-space-x-reverse: 0; + --tw-border-style: solid; + --tw-leading: initial; + --tw-font-weight: initial; + --tw-tracking: initial; + --tw-ordinal: initial; + --tw-slashed-zero: initial; + --tw-numeric-figure: initial; + --tw-numeric-spacing: initial; + --tw-numeric-fraction: initial; + --tw-shadow: 0 0 #0000; + --tw-shadow-color: initial; + --tw-shadow-alpha: 100%; + --tw-inset-shadow: 0 0 #0000; + --tw-inset-shadow-color: initial; + --tw-inset-shadow-alpha: 100%; + --tw-ring-color: initial; + --tw-ring-shadow: 0 0 #0000; + --tw-inset-ring-color: initial; + --tw-inset-ring-shadow: 0 0 #0000; + --tw-ring-inset: initial; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-offset-shadow: 0 0 #0000; + --tw-outline-style: solid; + --tw-blur: initial; + --tw-brightness: initial; + --tw-contrast: initial; + --tw-grayscale: initial; + --tw-hue-rotate: initial; + --tw-invert: initial; + --tw-opacity: initial; + --tw-saturate: initial; + --tw-sepia: initial; + --tw-drop-shadow: initial; + --tw-drop-shadow-color: initial; + --tw-drop-shadow-alpha: 100%; + --tw-drop-shadow-size: initial; + --tw-backdrop-blur: initial; + --tw-backdrop-brightness: initial; + --tw-backdrop-contrast: initial; + --tw-backdrop-grayscale: initial; + --tw-backdrop-hue-rotate: initial; + --tw-backdrop-invert: initial; + --tw-backdrop-opacity: initial; + --tw-backdrop-saturate: initial; + --tw-backdrop-sepia: initial; + --tw-duration: initial; + --tw-ease: initial; + --tw-content: ""; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-scale-z: 1; + --tw-animation-delay: 0s; + --tw-animation-direction: normal; + --tw-animation-duration: initial; + --tw-animation-fill-mode: none; + --tw-animation-iteration-count: 1; + --tw-enter-blur: 0; + --tw-enter-opacity: 1; + --tw-enter-rotate: 0; + --tw-enter-scale: 1; + --tw-enter-translate-x: 0; + --tw-enter-translate-y: 0; + --tw-exit-blur: 0; + --tw-exit-opacity: 1; + --tw-exit-rotate: 0; + --tw-exit-scale: 1; + --tw-exit-translate-x: 0; + --tw-exit-translate-y: 0 + } + } +} +@layer theme { + :root, :host { + --color-black: #000; + --spacing: .25rem; + --container-sm: 24rem; + --container-md: 28rem; + --container-lg: 32rem; + --container-xl: 36rem; + --container-4xl: 56rem; + --container-5xl: 64rem; + --container-6xl: 72rem; + --text-xs: .75rem; + --text-xs--line-height: calc(1 / .75); + --text-sm: .875rem; + --text-sm--line-height: calc(1.25 / .875); + --text-base: 1rem; + --text-base--line-height: calc(1.5 / 1); + --text-lg: 1.125rem; + --text-lg--line-height: calc(1.75 / 1.125); + --text-xl: 1.25rem; + --text-xl--line-height: calc(1.75 / 1.25); + --text-2xl: 1.5rem; + --text-2xl--line-height: calc(2 / 1.5); + --text-3xl: 1.875rem; + --text-3xl--line-height: calc(2.25 / 1.875); + --text-4xl: 2.25rem; + --text-4xl--line-height: calc(2.5 / 2.25); + --text-6xl: 3.75rem; + --text-6xl--line-height: 1; + --text-7xl: 4.5rem; + --text-7xl--line-height: 1; + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + --tracking-tight: -.025em; + --tracking-wide: .025em; + --tracking-widest: .1em; + --leading-relaxed: 1.625; + --radius-2xl: calc(var(--radius) + 8px); + --ease-out: cubic-bezier(0, 0, .2, 1); + --ease-in-out: cubic-bezier(.4, 0, .2, 1); + --animate-pulse: pulse 2s cubic-bezier(.4, 0, .6, 1) infinite; + --blur-xl: 24px; + --aspect-video: 16 / 9; + --default-transition-duration: .15s; + --default-transition-timing-function: cubic-bezier(.4, 0, .2, 1); + --default-font-family: var(--font-body); + --default-mono-font-family: var(--font-code); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-border: var(--border) + } +} +@layer base { + *, :after, :before, ::backdrop { + box-sizing: border-box; + border: 0 solid; + margin: 0; + padding: 0 + } + + ::file-selector-button { + box-sizing: border-box; + border: 0 solid; + margin: 0; + padding: 0 + } + + html, :host { + -webkit-text-size-adjust: 100%; + tab-size: 4; + line-height: 1.5; + font-family: var(--default-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); + font-feature-settings: var(--default-font-feature-settings, normal); + font-variation-settings: var(--default-font-variation-settings, normal); + -webkit-tap-highlight-color: transparent + } + + hr { + height: 0; + color: inherit; + border-top-width: 1px + } + + abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted + } + + h1, h2, h3, h4, h5, h6 { + font-size: inherit; + font-weight: inherit + } + + a { + color: inherit; + -webkit-text-decoration: inherit; + -webkit-text-decoration: inherit; + -webkit-text-decoration: inherit; + -webkit-text-decoration: inherit; + -webkit-text-decoration: inherit; + text-decoration: inherit + } + + b, strong { + font-weight: bolder + } + + code, kbd, samp, pre { + font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); + font-feature-settings: var(--default-mono-font-feature-settings, normal); + font-variation-settings: var(--default-mono-font-variation-settings, normal); + font-size: 1em + } + + small { + font-size: 80% + } + + sub, sup { + vertical-align: baseline; + font-size: 75%; + line-height: 0; + position: relative + } + + sub { + bottom: -.25em + } + + sup { + top: -.5em + } + + table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse + } + + :-moz-focusring:where(:not(iframe)) { + outline: auto + } + + progress { + vertical-align: baseline + } + + summary { + display: list-item + } + + ol, ul, menu { + list-style: none + } + + img, svg, video, canvas, audio, iframe, embed, object { + vertical-align: middle; + display: block + } + + img, video { + max-width: 100%; + height: auto + } + + button, input, select, optgroup, textarea { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + opacity: 1; + background-color: #0000; + border-radius: 0 + } + + ::file-selector-button { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + opacity: 1; + background-color: #0000; + border-radius: 0 + } + + :where(select:is([multiple],[size])) optgroup { + font-weight: bolder + } + + :where(select:is([multiple],[size])) optgroup option { + padding-inline-start: 20px + } + + ::file-selector-button { + margin-inline-end: 4px + } + + ::placeholder { + opacity: 1 + } + + @supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px) { + ::placeholder { + color: currentColor + } + + @supports (color:color-mix(in lab, red, red)) { + ::placeholder { + color: color-mix(in oklab, currentcolor 50%, transparent) + } + } + }textarea { + resize: vertical + } + + ::-webkit-search-decoration { + -webkit-appearance: none + } + + ::-webkit-date-and-time-value { + min-height: 1lh; + text-align: inherit + } + + ::-webkit-datetime-edit { + display: inline-flex + } + + ::-webkit-datetime-edit-fields-wrapper { + padding: 0 + } + + ::-webkit-datetime-edit { + padding-block: 0 + } + + ::-webkit-datetime-edit-year-field { + padding-block: 0 + } + + ::-webkit-datetime-edit-month-field { + padding-block: 0 + } + + ::-webkit-datetime-edit-day-field { + padding-block: 0 + } + + ::-webkit-datetime-edit-hour-field { + padding-block: 0 + } + + ::-webkit-datetime-edit-minute-field { + padding-block: 0 + } + + ::-webkit-datetime-edit-second-field { + padding-block: 0 + } + + ::-webkit-datetime-edit-millisecond-field { + padding-block: 0 + } + + ::-webkit-datetime-edit-meridiem-field { + padding-block: 0 + } + + ::-webkit-calendar-picker-indicator { + line-height: 1 + } + + :-moz-ui-invalid { + box-shadow: none + } + + button, input:where([type=button],[type=reset],[type=submit]) { + appearance: button + } + + ::file-selector-button { + appearance: button + } + + ::-webkit-inner-spin-button { + height: auto + } + + ::-webkit-outer-spin-button { + height: auto + } + + [hidden]:where(:not([hidden=until-found])) { + display: none !important + } + + * { + border-color: var(--color-border) + } + + body { + background-color: var(--color-background); + color: var(--color-foreground); + font-family: var(--font-body); + -webkit-font-smoothing: antialiased + } + + h1, h2, h3, h4 { + letter-spacing: -.02em; + font-family: Onest, system-ui, sans-serif; + font-weight: 500 + } +} +@layer components; +@layer utilities { + .pointer-events-none { + pointer-events: none + } + + .invisible { + visibility: hidden + } + + .visible { + visibility: visible + } + + .sr-only { + clip-path: inset(50%); + white-space: nowrap; + border-width: 0; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + position: absolute; + overflow: hidden + } + + .absolute { + position: absolute + } + + .fixed { + position: fixed + } + + .relative { + position: relative + } + + .sticky { + position: sticky + } + + .inset-0 { + inset: 0 + } + + .inset-x-0 { + inset-inline: 0 + } + + .inset-y-0 { + inset-block: 0 + } + + .-top-12 { + top: calc(var(--spacing) * -12) + } + + .top-0 { + top: 0 + } + + .top-1\.5 { + top: calc(var(--spacing) * 1.5) + } + + .top-1\/2 { + top: 50% + } + + .top-3\.5 { + top: calc(var(--spacing) * 3.5) + } + + .top-4 { + top: calc(var(--spacing) * 4) + } + + .top-\[1px\] { + top: 1px + } + + .top-\[50\%\] { + top: 50% + } + + .top-\[60\%\] { + top: 60% + } + + .top-full { + top: 100% + } + + .-right-12 { + right: calc(var(--spacing) * -12) + } + + .right-0 { + right: 0 + } + + .right-1 { + right: var(--spacing) + } + + .right-2 { + right: calc(var(--spacing) * 2) + } + + .right-3 { + right: calc(var(--spacing) * 3) + } + + .right-4 { + right: calc(var(--spacing) * 4) + } + + .-bottom-3 { + bottom: calc(var(--spacing) * -3) + } + + .-bottom-12 { + bottom: calc(var(--spacing) * -12) + } + + .bottom-0 { + bottom: 0 + } + + .-left-12 { + left: calc(var(--spacing) * -12) + } + + .left-0 { + left: 0 + } + + .left-1\/2 { + left: 50% + } + + .left-2 { + left: calc(var(--spacing) * 2) + } + + .left-6 { + left: calc(var(--spacing) * 6) + } + + .left-\[50\%\] { + left: 50% + } + + .isolate { + isolation: isolate + } + + .z-10 { + z-index: 10 + } + + .z-20 { + z-index: 20 + } + + .z-30 { + z-index: 30 + } + + .z-50 { + z-index: 50 + } + + .z-\[1\] { + z-index: 1 + } + + .z-\[9999\] { + z-index: 9999 + } + + .z-\[10000\] { + z-index: 10000 + } + + .order-1 { + order: 1 + } + + .order-2 { + order: 2 + } + + .-mx-1 { + margin-inline: calc(var(--spacing) * -1) + } + + .mx-2 { + margin-inline: calc(var(--spacing) * 2) + } + + .mx-3\.5 { + margin-inline: calc(var(--spacing) * 3.5) + } + + .mx-auto { + margin-inline: auto + } + + .my-0\.5 { + margin-block: calc(var(--spacing) * .5) + } + + .my-1 { + margin-block: var(--spacing) + } + + .-mt-4 { + margin-top: calc(var(--spacing) * -4) + } + + .-mt-\[0\.12em\] { + margin-top: -.12em + } + + .mt-1\.5 { + margin-top: calc(var(--spacing) * 1.5) + } + + .mt-2 { + margin-top: calc(var(--spacing) * 2) + } + + .mt-3 { + margin-top: calc(var(--spacing) * 3) + } + + .mt-4 { + margin-top: calc(var(--spacing) * 4) + } + + .mt-5 { + margin-top: calc(var(--spacing) * 5) + } + + .mt-6 { + margin-top: calc(var(--spacing) * 6) + } + + .mt-8 { + margin-top: calc(var(--spacing) * 8) + } + + .mt-10 { + margin-top: calc(var(--spacing) * 10) + } + + .mt-12 { + margin-top: calc(var(--spacing) * 12) + } + + .mt-16 { + margin-top: calc(var(--spacing) * 16) + } + + .mt-24 { + margin-top: calc(var(--spacing) * 24) + } + + .mt-auto { + margin-top: auto + } + + .mr-2 { + margin-right: calc(var(--spacing) * 2) + } + + .-mb-\[0\.22em\] { + margin-bottom: -.22em + } + + .mb-1 { + margin-bottom: var(--spacing) + } + + .-ml-4 { + margin-left: calc(var(--spacing) * -4) + } + + .ml-1 { + margin-left: var(--spacing) + } + + .ml-2 { + margin-left: calc(var(--spacing) * 2) + } + + .ml-auto { + margin-left: auto + } + + .block { + display: block + } + + .flex { + display: flex + } + + .grid { + display: grid + } + + .hidden { + display: none + } + + .inline-block { + display: inline-block + } + + .inline-flex { + display: inline-flex + } + + .table { + display: table + } + + .aspect-square { + aspect-ratio: 1 + } + + .aspect-video { + aspect-ratio: var(--aspect-video) + } + + .size-\(--cell-size\) { + width: var(--cell-size); + height: var(--cell-size) + } + + .size-2 { + width: calc(var(--spacing) * 2); + height: calc(var(--spacing) * 2) + } + + .size-3 { + width: calc(var(--spacing) * 3); + height: calc(var(--spacing) * 3) + } + + .size-4 { + width: calc(var(--spacing) * 4); + height: calc(var(--spacing) * 4) + } + + .size-5 { + width: calc(var(--spacing) * 5); + height: calc(var(--spacing) * 5) + } + + .size-6 { + width: calc(var(--spacing) * 6); + height: calc(var(--spacing) * 6) + } + + .size-9 { + width: calc(var(--spacing) * 9); + height: calc(var(--spacing) * 9) + } + + .size-\[5px\] { + width: 5px; + height: 5px + } + + .size-\[18px\] { + width: 18px; + height: 18px + } + + .h-\(--cell-size\) { + height: var(--cell-size) + } + + .h-1\.5 { + height: calc(var(--spacing) * 1.5) + } + + .h-2 { + height: calc(var(--spacing) * 2) + } + + .h-2\.5 { + height: calc(var(--spacing) * 2.5) + } + + .h-3 { + height: calc(var(--spacing) * 3) + } + + .h-3\.5 { + height: calc(var(--spacing) * 3.5) + } + + .h-4 { + height: calc(var(--spacing) * 4) + } + + .h-5 { + height: calc(var(--spacing) * 5) + } + + .h-6 { + height: calc(var(--spacing) * 6) + } + + .h-7 { + height: calc(var(--spacing) * 7) + } + + .h-8 { + height: calc(var(--spacing) * 8) + } + + .h-9 { + height: calc(var(--spacing) * 9) + } + + .h-10 { + height: calc(var(--spacing) * 10) + } + + .h-12 { + height: calc(var(--spacing) * 12) + } + + .h-16 { + height: calc(var(--spacing) * 16) + } + + .h-28 { + height: calc(var(--spacing) * 28) + } + + .h-\[1px\] { + height: 1px + } + + .h-\[var\(--radix-navigation-menu-viewport-height\)\] { + height: var(--radix-navigation-menu-viewport-height) + } + + .h-\[var\(--radix-select-trigger-height\)\] { + height: var(--radix-select-trigger-height) + } + + .h-auto { + height: auto + } + + .h-full { + height: 100% + } + + .h-px { + height: 1px + } + + .h-svh { + height: 100svh + } + + .max-h-\(--radix-context-menu-content-available-height\) { + max-height: var(--radix-context-menu-content-available-height) + } + + .max-h-\(--radix-select-content-available-height\) { + max-height: var(--radix-select-content-available-height) + } + + .max-h-\[300px\] { + max-height: 300px + } + + .max-h-\[var\(--radix-dropdown-menu-content-available-height\)\] { + max-height: var(--radix-dropdown-menu-content-available-height) + } + + .min-h-0 { + min-height: 0 + } + + .min-h-\[60px\] { + min-height: 60px + } + + .min-h-screen { + min-height: 100vh + } + + .min-h-svh { + min-height: 100svh + } + + .w-\(--cell-size\) { + width: var(--cell-size) + } + + .w-\(--sidebar-width\) { + width: var(--sidebar-width) + } + + .w-0 { + width: 0 + } + + .w-1 { + width: var(--spacing) + } + + .w-2 { + width: calc(var(--spacing) * 2) + } + + .w-2\.5 { + width: calc(var(--spacing) * 2.5) + } + + .w-3 { + width: calc(var(--spacing) * 3) + } + + .w-3\.5 { + width: calc(var(--spacing) * 3.5) + } + + .w-3\/4 { + width: 75% + } + + .w-4 { + width: calc(var(--spacing) * 4) + } + + .w-5 { + width: calc(var(--spacing) * 5) + } + + .w-7 { + width: calc(var(--spacing) * 7) + } + + .w-8 { + width: calc(var(--spacing) * 8) + } + + .w-9 { + width: calc(var(--spacing) * 9) + } + + .w-10 { + width: calc(var(--spacing) * 10) + } + + .w-16 { + width: calc(var(--spacing) * 16) + } + + .w-64 { + width: calc(var(--spacing) * 64) + } + + .w-72 { + width: calc(var(--spacing) * 72) + } + + .w-\[1px\] { + width: 1px + } + + .w-\[100px\] { + width: 100px + } + + .w-auto { + width: auto + } + + .w-fit { + width: fit-content + } + + .w-full { + width: 100% + } + + .w-max { + width: max-content + } + + .w-px { + width: 1px + } + + .max-w-\(--skeleton-width\) { + max-width: var(--skeleton-width) + } + + .max-w-4xl { + max-width: var(--container-4xl) + } + + .max-w-5xl { + max-width: var(--container-5xl) + } + + .max-w-6xl { + max-width: var(--container-6xl) + } + + .max-w-\[1180px\] { + max-width: 1180px + } + + .max-w-lg { + max-width: var(--container-lg) + } + + .max-w-max { + max-width: max-content + } + + .max-w-md { + max-width: var(--container-md) + } + + .max-w-xl { + max-width: var(--container-xl) + } + + .min-w-\(--cell-size\) { + min-width: var(--cell-size) + } + + .min-w-0 { + min-width: 0 + } + + .min-w-5 { + min-width: calc(var(--spacing) * 5) + } + + .min-w-8 { + min-width: calc(var(--spacing) * 8) + } + + .min-w-9 { + min-width: calc(var(--spacing) * 9) + } + + .min-w-10 { + min-width: calc(var(--spacing) * 10) + } + + .min-w-\[8rem\] { + min-width: 8rem + } + + .min-w-\[12rem\] { + min-width: 12rem + } + + .min-w-\[var\(--radix-select-trigger-width\)\] { + min-width: var(--radix-select-trigger-width) + } + + .flex-1 { + flex: 1 + } + + .shrink-0 { + flex-shrink: 0 + } + + .grow { + flex-grow: 1 + } + + .grow-0 { + flex-grow: 0 + } + + .basis-full { + flex-basis: 100% + } + + .caption-bottom { + caption-side: bottom + } + + .border-collapse { + border-collapse: collapse + } + + .origin-\(--radix-context-menu-content-transform-origin\) { + transform-origin: var(--radix-context-menu-content-transform-origin) + } + + .origin-\(--radix-dropdown-menu-content-transform-origin\) { + transform-origin: var(--radix-dropdown-menu-content-transform-origin) + } + + .origin-\(--radix-hover-card-content-transform-origin\) { + transform-origin: var(--radix-hover-card-content-transform-origin) + } + + .origin-\(--radix-menubar-content-transform-origin\) { + transform-origin: var(--radix-menubar-content-transform-origin) + } + + .origin-\(--radix-popover-content-transform-origin\) { + transform-origin: var(--radix-popover-content-transform-origin) + } + + .origin-\(--radix-select-content-transform-origin\) { + transform-origin: var(--radix-select-content-transform-origin) + } + + .origin-\(--radix-tooltip-content-transform-origin\) { + transform-origin: var(--radix-tooltip-content-transform-origin) + } + + .-translate-x-1\/2 { + --tw-translate-x: calc(calc(1 / 2 * 100%) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .-translate-x-px { + --tw-translate-x: -1px; + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .translate-x-\[-50\%\] { + --tw-translate-x: -50%; + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .translate-x-px { + --tw-translate-x: 1px; + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .-translate-y-1\/2 { + --tw-translate-y: calc(calc(1 / 2 * 100%) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .translate-y-\[-50\%\] { + --tw-translate-y: -50%; + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .rotate-45 { + rotate: 45deg + } + + .rotate-90 { + rotate: 90deg + } + + .transform { + transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,) + } + + .animate-caret-blink { + animation: 1.25s ease-out infinite caret-blink + } + + .animate-halo-drift { + animation: 10s ease-in-out infinite halo-drift + } + + .animate-in { + animation: enter var(--tw-animation-duration, var(--tw-duration, .15s)) var(--tw-ease, ease) var(--tw-animation-delay, 0s) var(--tw-animation-iteration-count, 1) var(--tw-animation-direction, normal) var(--tw-animation-fill-mode, none) + } + + .animate-pulse { + animation: var(--animate-pulse) + } + + .cursor-default { + cursor: default + } + + .cursor-pointer { + cursor: pointer + } + + .touch-none { + touch-action: none + } + + .resize { + resize: both + } + + .list-none { + list-style-type: none + } + + .grid-cols-\[3ch_1fr\] { + grid-template-columns:3ch 1fr + } + + .flex-col { + flex-direction: column + } + + .flex-col-reverse { + flex-direction: column-reverse + } + + .flex-row { + flex-direction: row + } + + .flex-wrap { + flex-wrap: wrap + } + + .place-content-center { + place-content: center + } + + .place-items-center { + place-items: center + } + + .items-center { + align-items: center + } + + .items-end { + align-items: flex-end + } + + .items-start { + align-items: flex-start + } + + .items-stretch { + align-items: stretch + } + + .justify-between { + justify-content: space-between + } + + .justify-center { + justify-content: center + } + + .gap-1 { + gap: var(--spacing) + } + + .gap-1\.5 { + gap: calc(var(--spacing) * 1.5) + } + + .gap-2 { + gap: calc(var(--spacing) * 2) + } + + .gap-2\.5 { + gap: calc(var(--spacing) * 2.5) + } + + .gap-3 { + gap: calc(var(--spacing) * 3) + } + + .gap-4 { + gap: calc(var(--spacing) * 4) + } + + .gap-6 { + gap: calc(var(--spacing) * 6) + } + + .gap-8 { + gap: calc(var(--spacing) * 8) + } + + .gap-10 { + gap: calc(var(--spacing) * 10) + } + + .gap-12 { + gap: calc(var(--spacing) * 12) + } + + :where(.space-y-1\.5>:not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse))) + } + + :where(.space-y-2>:not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse))) + } + + .gap-x-4 { + column-gap: calc(var(--spacing) * 4) + } + + .gap-x-12 { + column-gap: calc(var(--spacing) * 12) + } + + .gap-x-14 { + column-gap: calc(var(--spacing) * 14) + } + + :where(.space-x-1>:not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(var(--spacing) * var(--tw-space-x-reverse)); + margin-inline-end: calc(var(--spacing) * calc(1 - var(--tw-space-x-reverse))) + } + + .gap-y-3 { + row-gap: calc(var(--spacing) * 3) + } + + .gap-y-6 { + row-gap: calc(var(--spacing) * 6) + } + + .gap-y-8 { + row-gap: calc(var(--spacing) * 8) + } + + .overflow-auto { + overflow: auto + } + + .overflow-hidden { + overflow: hidden + } + + .overflow-x-auto { + overflow-x: auto + } + + .overflow-x-hidden { + overflow-x: hidden + } + + .overflow-y-auto { + overflow-y: auto + } + + .panel { + background: linear-gradient(180deg, var(--card), var(--background)) + } + + @supports (color:color-mix(in lab, red, red)) { + .panel { + background: linear-gradient(180deg, color-mix(in oklab, var(--card) 92%, transparent), color-mix(in oklab, var(--background) 92%, transparent)) + } + }.panel { + border: 1px solid var(--border); + border-radius: var(--radius-2xl) + } + + .rounded { + border-radius: .25rem + } + + .rounded-2xl { + border-radius: calc(var(--radius) + 8px) + } + + .rounded-\[2px\] { + border-radius: 2px + } + + .rounded-\[5px\] { + border-radius: 5px + } + + .rounded-\[inherit\] { + border-radius: inherit + } + + .rounded-full { + border-radius: 2147483647px + } + + .rounded-lg { + border-radius: var(--radius) + } + + .rounded-md { + border-radius: calc(var(--radius) - 2px) + } + + .rounded-none { + border-radius: 0 + } + + .rounded-sm { + border-radius: calc(var(--radius) - 4px) + } + + .rounded-xl { + border-radius: calc(var(--radius) + 4px) + } + + .rounded-t-\[10px\] { + border-top-left-radius: 10px; + border-top-right-radius: 10px + } + + .rounded-l-md { + border-top-left-radius: calc(var(--radius) - 2px); + border-bottom-left-radius: calc(var(--radius) - 2px) + } + + .rounded-tl-sm { + border-top-left-radius: calc(var(--radius) - 4px) + } + + .rounded-r-md { + border-top-right-radius: calc(var(--radius) - 2px); + border-bottom-right-radius: calc(var(--radius) - 2px) + } + + .border { + border-style: var(--tw-border-style); + border-width: 1px + } + + .border-2 { + border-style: var(--tw-border-style); + border-width: 2px + } + + .border-\[1\.5px\] { + border-style: var(--tw-border-style); + border-width: 1.5px + } + + .border-y { + border-block-style: var(--tw-border-style); + border-block-width: 1px + } + + .border-t { + border-top-style: var(--tw-border-style); + border-top-width: 1px + } + + .border-r { + border-right-style: var(--tw-border-style); + border-right-width: 1px + } + + .border-b { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 1px + } + + .border-l { + border-left-style: var(--tw-border-style); + border-left-width: 1px + } + + .border-l-2 { + border-left-style: var(--tw-border-style); + border-left-width: 2px + } + + .border-dashed { + --tw-border-style: dashed; + border-style: dashed + } + + .border-\(--color-border\) { + border-color: var(--color-border) + } + + .border-border, .border-border\/50 { + border-color: var(--border) + } + + @supports (color:color-mix(in lab, red, red)) { + .border-border\/50 { + border-color: color-mix(in oklab, var(--border) 50%, transparent) + } + }.border-brand-mid { + border-color: var(--brand-mid) + } + + .border-destructive\/50 { + border-color: var(--destructive) + } + + @supports (color:color-mix(in lab, red, red)) { + .border-destructive\/50 { + border-color: color-mix(in oklab, var(--destructive) 50%, transparent) + } + }.border-input { + border-color: var(--input) + } + + .border-primary, .border-primary\/50 { + border-color: var(--primary) + } + + @supports (color:color-mix(in lab, red, red)) { + .border-primary\/50 { + border-color: color-mix(in oklab, var(--primary) 50%, transparent) + } + }.border-primary\/70 { + border-color: var(--primary) + } + + @supports (color:color-mix(in lab, red, red)) { + .border-primary\/70 { + border-color: color-mix(in oklab, var(--primary) 70%, transparent) + } + }.border-transparent { + border-color: #0000 + } + + .border-t-transparent { + border-top-color: #0000 + } + + .border-l-transparent { + border-left-color: #0000 + } + + .bg-\(--color-bg\) { + background-color: var(--color-bg) + } + + .bg-accent { + background-color: var(--accent) + } + + .bg-background, .bg-background\/80 { + background-color: var(--background) + } + + @supports (color:color-mix(in lab, red, red)) { + .bg-background\/80 { + background-color: color-mix(in oklab, var(--background) 80%, transparent) + } + }.bg-background\/95 { + background-color: var(--background) + } + + @supports (color:color-mix(in lab, red, red)) { + .bg-background\/95 { + background-color: color-mix(in oklab, var(--background) 95%, transparent) + } + }.bg-black\/80 { + background-color: #000c + } + + @supports (color:color-mix(in lab, red, red)) { + .bg-black\/80 { + background-color: color-mix(in oklab, var(--color-black) 80%, transparent) + } + }.bg-border { + background-color: var(--border) + } + + .bg-card { + background-color: var(--card) + } + + .bg-code-bg { + background-color: var(--code-bg) + } + + .bg-destructive { + background-color: var(--destructive) + } + + .bg-foreground, .bg-foreground\/\[0\.02\] { + background-color: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .bg-foreground\/\[0\.02\] { + background-color: color-mix(in oklab, var(--foreground) 2%, transparent) + } + }.bg-muted, .bg-muted\/50 { + background-color: var(--muted) + } + + @supports (color:color-mix(in lab, red, red)) { + .bg-muted\/50 { + background-color: color-mix(in oklab, var(--muted) 50%, transparent) + } + }.bg-popover { + background-color: var(--popover) + } + + .bg-primary, .bg-primary\/10 { + background-color: var(--primary) + } + + @supports (color:color-mix(in lab, red, red)) { + .bg-primary\/10 { + background-color: color-mix(in oklab, var(--primary) 10%, transparent) + } + }.bg-primary\/12 { + background-color: var(--primary) + } + + @supports (color:color-mix(in lab, red, red)) { + .bg-primary\/12 { + background-color: color-mix(in oklab, var(--primary) 12%, transparent) + } + }.bg-primary\/20 { + background-color: var(--primary) + } + + @supports (color:color-mix(in lab, red, red)) { + .bg-primary\/20 { + background-color: color-mix(in oklab, var(--primary) 20%, transparent) + } + }.bg-primary\/60 { + background-color: var(--primary) + } + + @supports (color:color-mix(in lab, red, red)) { + .bg-primary\/60 { + background-color: color-mix(in oklab, var(--primary) 60%, transparent) + } + }.bg-secondary, .bg-secondary\/25 { + background-color: var(--secondary) + } + + @supports (color:color-mix(in lab, red, red)) { + .bg-secondary\/25 { + background-color: color-mix(in oklab, var(--secondary) 25%, transparent) + } + }.bg-secondary\/30 { + background-color: var(--secondary) + } + + @supports (color:color-mix(in lab, red, red)) { + .bg-secondary\/30 { + background-color: color-mix(in oklab, var(--secondary) 30%, transparent) + } + }.bg-secondary\/60 { + background-color: var(--secondary) + } + + @supports (color:color-mix(in lab, red, red)) { + .bg-secondary\/60 { + background-color: color-mix(in oklab, var(--secondary) 60%, transparent) + } + }.bg-transparent { + background-color: #0000 + } + + .bg-grid { + background-image: linear-gradient(90deg, oklch(100% 0 0/.04) 1px, #0000 1px), linear-gradient(oklch(100% 0 0/.04) 1px, #0000 1px); + background-size: 56px 56px + } + + .text-brand-gradient { + background-image: var(--gradient-brand); + color: #0000; + -webkit-background-clip: text; + background-clip: text + } + + .bg-halo { + background-image: var(--gradient-halo) + } + + .fill-background { + fill: var(--background) + } + + .fill-brand-mid, .fill-brand-mid\/60 { + fill: var(--brand-mid) + } + + @supports (color:color-mix(in lab, red, red)) { + .fill-brand-mid\/60 { + fill: color-mix(in oklab, var(--brand-mid) 60%, transparent) + } + }.fill-brand-soft\/14 { + fill: var(--brand-soft) + } + + @supports (color:color-mix(in lab, red, red)) { + .fill-brand-soft\/14 { + fill: color-mix(in oklab, var(--brand-soft) 14%, transparent) + } + }.fill-brand-soft\/24 { + fill: var(--brand-soft) + } + + @supports (color:color-mix(in lab, red, red)) { + .fill-brand-soft\/24 { + fill: color-mix(in oklab, var(--brand-soft) 24%, transparent) + } + }.fill-brand-soft\/25 { + fill: var(--brand-soft) + } + + @supports (color:color-mix(in lab, red, red)) { + .fill-brand-soft\/25 { + fill: color-mix(in oklab, var(--brand-soft) 25%, transparent) + } + }.fill-brand-soft\/40 { + fill: var(--brand-soft) + } + + @supports (color:color-mix(in lab, red, red)) { + .fill-brand-soft\/40 { + fill: color-mix(in oklab, var(--brand-soft) 40%, transparent) + } + }.fill-card { + fill: var(--card) + } + + .fill-current { + fill: currentColor + } + + .fill-foreground\/3 { + fill: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .fill-foreground\/3 { + fill: color-mix(in oklab, var(--foreground) 3%, transparent) + } + }.fill-foreground\/4 { + fill: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .fill-foreground\/4 { + fill: color-mix(in oklab, var(--foreground) 4%, transparent) + } + }.fill-foreground\/5 { + fill: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .fill-foreground\/5 { + fill: color-mix(in oklab, var(--foreground) 5%, transparent) + } + }.fill-foreground\/6 { + fill: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .fill-foreground\/6 { + fill: color-mix(in oklab, var(--foreground) 6%, transparent) + } + }.fill-foreground\/8 { + fill: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .fill-foreground\/8 { + fill: color-mix(in oklab, var(--foreground) 8%, transparent) + } + }.fill-foreground\/10 { + fill: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .fill-foreground\/10 { + fill: color-mix(in oklab, var(--foreground) 10%, transparent) + } + }.fill-foreground\/16 { + fill: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .fill-foreground\/16 { + fill: color-mix(in oklab, var(--foreground) 16%, transparent) + } + }.fill-none { + fill: none + } + + .fill-primary { + fill: var(--primary) + } + + .stroke-brand-soft\/60 { + stroke: var(--brand-soft) + } + + @supports (color:color-mix(in lab, red, red)) { + .stroke-brand-soft\/60 { + stroke: color-mix(in oklab, var(--brand-soft) 60%, transparent) + } + }.stroke-current { + stroke: currentColor + } + + .stroke-foreground\/25 { + stroke: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .stroke-foreground\/25 { + stroke: color-mix(in oklab, var(--foreground) 25%, transparent) + } + }.stroke-foreground\/40 { + stroke: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .stroke-foreground\/40 { + stroke: color-mix(in oklab, var(--foreground) 40%, transparent) + } + }.stroke-foreground\/45 { + stroke: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .stroke-foreground\/45 { + stroke: color-mix(in oklab, var(--foreground) 45%, transparent) + } + }.stroke-foreground\/\[0\.07\] { + stroke: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .stroke-foreground\/\[0\.07\] { + stroke: color-mix(in oklab, var(--foreground) 7.0%, transparent) + } + }.stroke-primary { + stroke: var(--primary) + } + + .object-contain { + object-fit: contain + } + + .p-0 { + padding: 0 + } + + .p-0\.5 { + padding: calc(var(--spacing) * .5) + } + + .p-1 { + padding: var(--spacing) + } + + .p-2 { + padding: calc(var(--spacing) * 2) + } + + .p-3 { + padding: calc(var(--spacing) * 3) + } + + .p-4 { + padding: calc(var(--spacing) * 4) + } + + .p-6 { + padding: calc(var(--spacing) * 6) + } + + .p-8 { + padding: calc(var(--spacing) * 8) + } + + .p-\[1px\] { + padding: 1px + } + + .px-\(--cell-size\) { + padding-inline: var(--cell-size) + } + + .px-1 { + padding-inline: var(--spacing) + } + + .px-1\.5 { + padding-inline: calc(var(--spacing) * 1.5) + } + + .px-2 { + padding-inline: calc(var(--spacing) * 2) + } + + .px-2\.5 { + padding-inline: calc(var(--spacing) * 2.5) + } + + .px-3 { + padding-inline: calc(var(--spacing) * 3) + } + + .px-4 { + padding-inline: calc(var(--spacing) * 4) + } + + .px-5 { + padding-inline: calc(var(--spacing) * 5) + } + + .px-6 { + padding-inline: calc(var(--spacing) * 6) + } + + .px-8 { + padding-inline: calc(var(--spacing) * 8) + } + + .py-0\.5 { + padding-block: calc(var(--spacing) * .5) + } + + .py-1 { + padding-block: var(--spacing) + } + + .py-1\.5 { + padding-block: calc(var(--spacing) * 1.5) + } + + .py-2 { + padding-block: calc(var(--spacing) * 2) + } + + .py-2\.5 { + padding-block: calc(var(--spacing) * 2.5) + } + + .py-3 { + padding-block: calc(var(--spacing) * 3) + } + + .py-4 { + padding-block: calc(var(--spacing) * 4) + } + + .py-5 { + padding-block: calc(var(--spacing) * 5) + } + + .py-6 { + padding-block: calc(var(--spacing) * 6) + } + + .py-8 { + padding-block: calc(var(--spacing) * 8) + } + + .py-16 { + padding-block: calc(var(--spacing) * 16) + } + + .py-24 { + padding-block: calc(var(--spacing) * 24) + } + + .pt-0 { + padding-top: 0 + } + + .pt-3 { + padding-top: calc(var(--spacing) * 3) + } + + .pt-4 { + padding-top: calc(var(--spacing) * 4) + } + + .pt-24 { + padding-top: calc(var(--spacing) * 24) + } + + .pt-\[0\.12em\] { + padding-top: .12em + } + + .pr-1 { + padding-right: var(--spacing) + } + + .pr-2 { + padding-right: calc(var(--spacing) * 2) + } + + .pr-2\.5 { + padding-right: calc(var(--spacing) * 2.5) + } + + .pr-8 { + padding-right: calc(var(--spacing) * 8) + } + + .pb-3 { + padding-bottom: calc(var(--spacing) * 3) + } + + .pb-4 { + padding-bottom: calc(var(--spacing) * 4) + } + + .pb-20 { + padding-bottom: calc(var(--spacing) * 20) + } + + .pb-24 { + padding-bottom: calc(var(--spacing) * 24) + } + + .pb-28 { + padding-bottom: calc(var(--spacing) * 28) + } + + .pb-\[0\.22em\] { + padding-bottom: .22em + } + + .pl-2 { + padding-left: calc(var(--spacing) * 2) + } + + .pl-2\.5 { + padding-left: calc(var(--spacing) * 2.5) + } + + .pl-3 { + padding-left: calc(var(--spacing) * 3) + } + + .pl-4 { + padding-left: calc(var(--spacing) * 4) + } + + .pl-8 { + padding-left: calc(var(--spacing) * 8) + } + + .text-center { + text-align: center + } + + .text-left { + text-align: left + } + + .text-right { + text-align: right + } + + .align-middle { + vertical-align: middle + } + + .font-syne { + font-family: Syne, system-ui, sans-serif; + font-style: normal; + font-weight: 700 + } + + .font-mono { + font-family: var(--font-code) + } + + .text-2xl { + font-size: var(--text-2xl); + line-height: var(--tw-leading, var(--text-2xl--line-height)) + } + + .text-3xl { + font-size: var(--text-3xl); + line-height: var(--tw-leading, var(--text-3xl--line-height)) + } + + .text-4xl { + font-size: var(--text-4xl); + line-height: var(--tw-leading, var(--text-4xl--line-height)) + } + + .text-7xl { + font-size: var(--text-7xl); + line-height: var(--tw-leading, var(--text-7xl--line-height)) + } + + .text-base { + font-size: var(--text-base); + line-height: var(--tw-leading, var(--text-base--line-height)) + } + + .text-lg { + font-size: var(--text-lg); + line-height: var(--tw-leading, var(--text-lg--line-height)) + } + + .text-sm { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)) + } + + .text-xl { + font-size: var(--text-xl); + line-height: var(--tw-leading, var(--text-xl--line-height)) + } + + .text-xs { + font-size: var(--text-xs); + line-height: var(--tw-leading, var(--text-xs--line-height)) + } + + .text-\[0\.8rem\] { + font-size: .8rem + } + + .text-\[10\.5px\] { + font-size: 10.5px + } + + .text-\[10px\] { + font-size: 10px + } + + .text-\[11px\] { + font-size: 11px + } + + .text-\[12\.5px\] { + font-size: 12.5px + } + + .leading-\[1\.08\] { + --tw-leading: 1.08; + line-height: 1.08 + } + + .leading-\[1\.75\] { + --tw-leading: 1.75; + line-height: 1.75 + } + + .leading-none { + --tw-leading: 1; + line-height: 1 + } + + .leading-relaxed { + --tw-leading: var(--leading-relaxed); + line-height: var(--leading-relaxed) + } + + .font-bold { + --tw-font-weight: var(--font-weight-bold); + font-weight: var(--font-weight-bold) + } + + .font-medium { + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium) + } + + .font-normal { + --tw-font-weight: var(--font-weight-normal); + font-weight: var(--font-weight-normal) + } + + .font-semibold { + --tw-font-weight: var(--font-weight-semibold); + font-weight: var(--font-weight-semibold) + } + + .tracking-\[0\.18em\] { + --tw-tracking: .18em; + letter-spacing: .18em + } + + .tracking-tight { + --tw-tracking: var(--tracking-tight); + letter-spacing: var(--tracking-tight) + } + + .tracking-wide { + --tw-tracking: var(--tracking-wide); + letter-spacing: var(--tracking-wide) + } + + .tracking-widest { + --tw-tracking: var(--tracking-widest); + letter-spacing: var(--tracking-widest) + } + + .text-balance { + text-wrap: balance + } + + .break-words { + overflow-wrap: break-word + } + + .whitespace-nowrap { + white-space: nowrap + } + + .text-accent-foreground { + color: var(--accent-foreground) + } + + .text-brand-mid { + color: var(--brand-mid) + } + + .text-brand-soft { + color: var(--brand-soft) + } + + .text-card-foreground { + color: var(--card-foreground) + } + + .text-code-comment { + color: var(--code-comment) + } + + .text-code-fn { + color: var(--code-fn) + } + + .text-code-keyword { + color: var(--code-keyword) + } + + .text-code-string { + color: var(--code-string) + } + + .text-current { + color: currentColor + } + + .text-destructive { + color: var(--destructive) + } + + .text-destructive-foreground { + color: var(--destructive-foreground) + } + + .text-foreground, .text-foreground\/25 { + color: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .text-foreground\/25 { + color: color-mix(in oklab, var(--foreground) 25%, transparent) + } + }.text-foreground\/70 { + color: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .text-foreground\/70 { + color: color-mix(in oklab, var(--foreground) 70%, transparent) + } + }.text-foreground\/80 { + color: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .text-foreground\/80 { + color: color-mix(in oklab, var(--foreground) 80%, transparent) + } + }.text-foreground\/85 { + color: var(--foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .text-foreground\/85 { + color: color-mix(in oklab, var(--foreground) 85%, transparent) + } + }.text-muted-foreground, .text-muted-foreground\/60 { + color: var(--muted-foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .text-muted-foreground\/60 { + color: color-mix(in oklab, var(--muted-foreground) 60%, transparent) + } + }.text-muted-foreground\/70 { + color: var(--muted-foreground) + } + + @supports (color:color-mix(in lab, red, red)) { + .text-muted-foreground\/70 { + color: color-mix(in oklab, var(--muted-foreground) 70%, transparent) + } + }.text-popover-foreground { + color: var(--popover-foreground) + } + + .text-primary { + color: var(--primary) + } + + .text-primary-foreground { + color: var(--primary-foreground) + } + + .text-secondary-foreground { + color: var(--secondary-foreground) + } + + .uppercase { + text-transform: uppercase + } + + .italic { + font-style: italic + } + + .tabular-nums { + --tw-numeric-spacing: tabular-nums; + font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,) + } + + .underline-offset-4 { + text-underline-offset: 4px + } + + .opacity-0 { + opacity: 0 + } + + .opacity-45 { + opacity: .45 + } + + .opacity-50 { + opacity: .5 + } + + .opacity-55 { + opacity: .55 + } + + .opacity-60 { + opacity: .6 + } + + .opacity-70 { + opacity: .7 + } + + .shadow { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, #0000001a), 0 1px 2px -1px var(--tw-shadow-color, #0000001a); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .shadow-\[0_0_0_1px_var\(--sidebar-border\)\] { + --tw-shadow: 0 0 0 1px var(--tw-shadow-color, var(--sidebar-border)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .shadow-lg { + --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, #0000001a), 0 4px 6px -4px var(--tw-shadow-color, #0000001a); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .shadow-md { + --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, #0000001a), 0 2px 4px -2px var(--tw-shadow-color, #0000001a); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .shadow-none { + --tw-shadow: 0 0 #0000; + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .shadow-sm { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, #0000001a), 0 1px 2px -1px var(--tw-shadow-color, #0000001a); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .shadow-xl { + --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, #0000001a), 0 8px 10px -6px var(--tw-shadow-color, #0000001a); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .shadow-xs { + --tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, #0000000d); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .ring-0 { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .ring-1 { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .shadow-brand { + box-shadow: var(--shadow-brand) + } + + .shadow-panel { + box-shadow: var(--shadow-panel) + } + + .ring-ring { + --tw-ring-color: var(--ring) + } + + .ring-offset-background { + --tw-ring-offset-color: var(--background) + } + + .outline { + outline-style: var(--tw-outline-style); + outline-width: 1px + } + + .grayscale { + --tw-grayscale: grayscale(100%); + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,) + } + + .filter { + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,) + } + + .backdrop-blur-xl { + --tw-backdrop-blur: blur(var(--blur-xl)); + -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); + backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,) + } + + .transition { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)) + } + + .transition-\[left\,right\,width\] { + transition-property: left, right, width; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)) + } + + .transition-\[margin\,opacity\] { + transition-property: margin, opacity; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)) + } + + .transition-\[max-height\,opacity\,filter\] { + transition-property: max-height, opacity, filter; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)) + } + + .transition-\[width\,height\,padding\] { + transition-property: width, height, padding; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)) + } + + .transition-\[width\] { + transition-property: width; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)) + } + + .transition-all { + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)) + } + + .transition-colors { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)) + } + + .transition-opacity { + transition-property: opacity; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)) + } + + .transition-transform { + transition-property: transform, translate, scale, rotate; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)) + } + + .duration-200 { + --tw-duration: .2s; + transition-duration: .2s + } + + .duration-300 { + --tw-duration: .3s; + transition-duration: .3s + } + + .duration-1000 { + --tw-duration: 1s; + transition-duration: 1s + } + + .duration-\[420ms\] { + --tw-duration: .42s; + transition-duration: .42s + } + + .ease-\[cubic-bezier\(0\.22\,0\.61\,0\.36\,1\)\] { + --tw-ease: cubic-bezier(.22, .61, .36, 1); + transition-timing-function: cubic-bezier(.22, .61, .36, 1) + } + + .ease-in-out { + --tw-ease: var(--ease-in-out); + transition-timing-function: var(--ease-in-out) + } + + .ease-linear { + --tw-ease: linear; + transition-timing-function: linear + } + + .ease-out { + --tw-ease: var(--ease-out); + transition-timing-function: var(--ease-out) + } + + .will-change-transform { + will-change: transform + } + + .fade-in-0 { + --tw-enter-opacity: 0 + } + + .outline-none { + --tw-outline-style: none; + outline-style: none + } + + .select-none { + -webkit-user-select: none; + user-select: none + } + + .zoom-in-95 { + --tw-enter-scale: .95 + } + + .\[--cell-size\:2rem\] { + --cell-size: 2rem + } + + .running { + animation-play-state: running + } + + .group-focus-within\/menu-item\:opacity-100:is(:where(.group\/menu-item):focus-within *) { + opacity: 1 + } + + @media (hover: hover) { + .group-hover\:text-primary:is(:where(.group):hover *) { + color: var(--primary) + } + + .group-hover\:opacity-100:is(:where(.group):hover *) { + opacity: 1 + } + + .group-hover\:grayscale-0:is(:where(.group):hover *) { + --tw-grayscale: grayscale(0%); + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,) + } + + .group-hover\/menu-item\:opacity-100:is(:where(.group\/menu-item):hover *) { + opacity: 1 + } + }.group-has-\[\[data-sidebar\=menu-action\]\]\/menu-item\:pr-8:is(:where(.group\/menu-item):has([data-sidebar=menu-action]) *) { + padding-right: calc(var(--spacing) * 8) + } + + .group-data-\[collapsible\=icon\]\:-mt-8:is(:where(.group)[data-collapsible=icon] *) { + margin-top: calc(var(--spacing) * -8) + } + + .group-data-\[collapsible\=icon\]\:hidden:is(:where(.group)[data-collapsible=icon] *) { + display: none + } + + .group-data-\[collapsible\=icon\]\:\!size-8:is(:where(.group)[data-collapsible=icon] *) { + width: calc(var(--spacing) * 8) !important; + height: calc(var(--spacing) * 8) !important + } + + .group-data-\[collapsible\=icon\]\:w-\(--sidebar-width-icon\):is(:where(.group)[data-collapsible=icon] *) { + width: var(--sidebar-width-icon) + } + + .group-data-\[collapsible\=icon\]\:w-\[calc\(var\(--sidebar-width-icon\)_\+_theme\(spacing\.4\)\)\]:is(:where(.group)[data-collapsible=icon] *) { + width: calc(var(--sidebar-width-icon) + 1rem) + } + + .group-data-\[collapsible\=icon\]\:w-\[calc\(var\(--sidebar-width-icon\)_\+_theme\(spacing\.4\)_\+2px\)\]:is(:where(.group)[data-collapsible=icon] *) { + width: calc(var(--sidebar-width-icon) + 1rem + 2px) + } + + .group-data-\[collapsible\=icon\]\:overflow-hidden:is(:where(.group)[data-collapsible=icon] *) { + overflow: hidden + } + + .group-data-\[collapsible\=icon\]\:\!p-0:is(:where(.group)[data-collapsible=icon] *) { + padding: 0 !important + } + + .group-data-\[collapsible\=icon\]\:\!p-2:is(:where(.group)[data-collapsible=icon] *) { + padding: calc(var(--spacing) * 2) !important + } + + .group-data-\[collapsible\=icon\]\:opacity-0:is(:where(.group)[data-collapsible=icon] *) { + opacity: 0 + } + + .group-data-\[collapsible\=offcanvas\]\:right-\[calc\(var\(--sidebar-width\)\*-1\)\]:is(:where(.group)[data-collapsible=offcanvas] *) { + right: calc(var(--sidebar-width) * -1) + } + + .group-data-\[collapsible\=offcanvas\]\:left-\[calc\(var\(--sidebar-width\)\*-1\)\]:is(:where(.group)[data-collapsible=offcanvas] *) { + left: calc(var(--sidebar-width) * -1) + } + + .group-data-\[collapsible\=offcanvas\]\:w-0:is(:where(.group)[data-collapsible=offcanvas] *) { + width: 0 + } + + .group-data-\[collapsible\=offcanvas\]\:translate-x-0:is(:where(.group)[data-collapsible=offcanvas] *) { + --tw-translate-x: 0px; + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .group-data-\[focused\=true\]\/day\:relative:is(:where(.group\/day)[data-focused=true] *) { + position: relative + } + + .group-data-\[focused\=true\]\/day\:z-10:is(:where(.group\/day)[data-focused=true] *) { + z-index: 10 + } + + .group-data-\[focused\=true\]\/day\:border-ring:is(:where(.group\/day)[data-focused=true] *) { + border-color: var(--ring) + } + + .group-data-\[focused\=true\]\/day\:ring-\[3px\]:is(:where(.group\/day)[data-focused=true] *) { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .group-data-\[focused\=true\]\/day\:ring-ring\/50:is(:where(.group\/day)[data-focused=true] *) { + --tw-ring-color: var(--ring) + } + + @supports (color:color-mix(in lab, red, red)) { + .group-data-\[focused\=true\]\/day\:ring-ring\/50:is(:where(.group\/day)[data-focused=true] *) { + --tw-ring-color: color-mix(in oklab, var(--ring) 50%, transparent) + } + }.group-data-\[side\=left\]\:-right-4:is(:where(.group)[data-side=left] *) { + right: calc(var(--spacing) * -4) + } + + .group-data-\[side\=left\]\:border-r:is(:where(.group)[data-side=left] *) { + border-right-style: var(--tw-border-style); + border-right-width: 1px + } + + .group-data-\[side\=right\]\:left-0:is(:where(.group)[data-side=right] *) { + left: 0 + } + + .group-data-\[side\=right\]\:rotate-180:is(:where(.group)[data-side=right] *) { + rotate: 180deg + } + + .group-data-\[side\=right\]\:border-l:is(:where(.group)[data-side=right] *) { + border-left-style: var(--tw-border-style); + border-left-width: 1px + } + + .group-data-\[state\=open\]\:rotate-180:is(:where(.group)[data-state=open] *) { + rotate: 180deg + } + + .group-data-\[variant\=floating\]\:rounded-lg:is(:where(.group)[data-variant=floating] *) { + border-radius: var(--radius) + } + + .group-data-\[variant\=floating\]\:border:is(:where(.group)[data-variant=floating] *) { + border-style: var(--tw-border-style); + border-width: 1px + } + + .group-data-\[variant\=floating\]\:shadow:is(:where(.group)[data-variant=floating] *) { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, #0000001a), 0 1px 2px -1px var(--tw-shadow-color, #0000001a); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .group-\[\.toast\]\:bg-muted:is(:where(.group).toast *) { + background-color: var(--muted) + } + + .group-\[\.toast\]\:bg-primary:is(:where(.group).toast *) { + background-color: var(--primary) + } + + .group-\[\.toast\]\:text-muted-foreground:is(:where(.group).toast *) { + color: var(--muted-foreground) + } + + .group-\[\.toast\]\:text-primary-foreground:is(:where(.group).toast *) { + color: var(--primary-foreground) + } + + .group-\[\.toaster\]\:border-border:is(:where(.group).toaster *) { + border-color: var(--border) + } + + .group-\[\.toaster\]\:bg-background:is(:where(.group).toaster *) { + background-color: var(--background) + } + + .group-\[\.toaster\]\:text-foreground:is(:where(.group).toaster *) { + color: var(--foreground) + } + + .group-\[\.toaster\]\:shadow-lg:is(:where(.group).toaster *) { + --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, #0000001a), 0 4px 6px -4px var(--tw-shadow-color, #0000001a); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*) { + cursor: not-allowed + } + + .peer-disabled\:opacity-70:is(:where(.peer):disabled~*) { + opacity: .7 + } + + .peer-data-\[size\=default\]\/menu-button\:top-1\.5:is(:where(.peer\/menu-button)[data-size=default]~*) { + top: calc(var(--spacing) * 1.5) + } + + .peer-data-\[size\=lg\]\/menu-button\:top-2\.5:is(:where(.peer\/menu-button)[data-size=lg]~*) { + top: calc(var(--spacing) * 2.5) + } + + .peer-data-\[size\=sm\]\/menu-button\:top-1:is(:where(.peer\/menu-button)[data-size=sm]~*) { + top: var(--spacing) + } + + .file\:border-0::file-selector-button { + border-style: var(--tw-border-style); + border-width: 0 + } + + .file\:bg-transparent::file-selector-button { + background-color: #0000 + } + + .file\:text-sm::file-selector-button { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)) + } + + .file\:font-medium::file-selector-button { + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium) + } + + .file\:text-foreground::file-selector-button { + color: var(--foreground) + } + + .placeholder\:text-muted-foreground::placeholder { + color: var(--muted-foreground) + } + + .after\:absolute:after { + content: var(--tw-content); + position: absolute + } + + .after\:-inset-2:after { + content: var(--tw-content); + inset: calc(var(--spacing) * -2) + } + + .after\:inset-y-0:after { + content: var(--tw-content); + inset-block: 0 + } + + .after\:left-1\/2:after { + content: var(--tw-content); + left: 50% + } + + .after\:w-1:after { + content: var(--tw-content); + width: var(--spacing) + } + + .after\:w-\[2px\]:after { + content: var(--tw-content); + width: 2px + } + + .after\:-translate-x-1\/2:after { + content: var(--tw-content); + --tw-translate-x: calc(calc(1 / 2 * 100%) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .group-data-\[collapsible\=offcanvas\]\:after\:left-full:is(:where(.group)[data-collapsible=offcanvas] *):after { + content: var(--tw-content); + left: 100% + } + + .first\:rounded-l-md:first-child { + border-top-left-radius: calc(var(--radius) - 2px); + border-bottom-left-radius: calc(var(--radius) - 2px) + } + + .first\:border-l:first-child { + border-left-style: var(--tw-border-style); + border-left-width: 1px + } + + .last\:rounded-r-md:last-child { + border-top-right-radius: calc(var(--radius) - 2px); + border-bottom-right-radius: calc(var(--radius) - 2px) + } + + @media (hover: hover) { + .hover\:scale-\[1\.02\]:hover { + scale: 1.02 + } + + .hover\:border-primary\/50:hover { + border-color: var(--primary) + } + + @supports (color:color-mix(in lab, red, red)) { + .hover\:border-primary\/50:hover { + border-color: color-mix(in oklab, var(--primary) 50%, transparent) + } + }.hover\:bg-accent:hover { + background-color: var(--accent) + } + + .hover\:bg-brand-mid\/10:hover { + background-color: var(--brand-mid) + } + + @supports (color:color-mix(in lab, red, red)) { + .hover\:bg-brand-mid\/10:hover { + background-color: color-mix(in oklab, var(--brand-mid) 10%, transparent) + } + }.hover\:bg-destructive\/80:hover { + background-color: var(--destructive) + } + + @supports (color:color-mix(in lab, red, red)) { + .hover\:bg-destructive\/80:hover { + background-color: color-mix(in oklab, var(--destructive) 80%, transparent) + } + }.hover\:bg-destructive\/90:hover { + background-color: var(--destructive) + } + + @supports (color:color-mix(in lab, red, red)) { + .hover\:bg-destructive\/90:hover { + background-color: color-mix(in oklab, var(--destructive) 90%, transparent) + } + }.hover\:bg-muted:hover, .hover\:bg-muted\/50:hover { + background-color: var(--muted) + } + + @supports (color:color-mix(in lab, red, red)) { + .hover\:bg-muted\/50:hover { + background-color: color-mix(in oklab, var(--muted) 50%, transparent) + } + }.hover\:bg-primary\/80:hover { + background-color: var(--primary) + } + + @supports (color:color-mix(in lab, red, red)) { + .hover\:bg-primary\/80:hover { + background-color: color-mix(in oklab, var(--primary) 80%, transparent) + } + }.hover\:bg-primary\/90:hover { + background-color: var(--primary) + } + + @supports (color:color-mix(in lab, red, red)) { + .hover\:bg-primary\/90:hover { + background-color: color-mix(in oklab, var(--primary) 90%, transparent) + } + }.hover\:bg-secondary\/80:hover { + background-color: var(--secondary) + } + + @supports (color:color-mix(in lab, red, red)) { + .hover\:bg-secondary\/80:hover { + background-color: color-mix(in oklab, var(--secondary) 80%, transparent) + } + }.hover\:text-accent-foreground:hover { + color: var(--accent-foreground) + } + + .hover\:text-foreground:hover { + color: var(--foreground) + } + + .hover\:text-muted-foreground:hover { + color: var(--muted-foreground) + } + + .hover\:text-primary:hover { + color: var(--primary) + } + + .hover\:underline:hover { + text-decoration-line: underline + } + + .hover\:opacity-90:hover { + opacity: .9 + } + + .hover\:opacity-100:hover { + opacity: 1 + } + + .hover\:shadow-\[0_0_0_1px_var\(--sidebar-accent\)\]:hover { + --tw-shadow: 0 0 0 1px var(--tw-shadow-color, var(--sidebar-accent)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + }.focus\:bg-accent:focus { + background-color: var(--accent) + } + + .focus\:text-accent-foreground:focus { + color: var(--accent-foreground) + } + + .focus\:ring-1:focus { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .focus\:ring-2:focus { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .focus\:ring-ring:focus { + --tw-ring-color: var(--ring) + } + + .focus\:ring-offset-2:focus { + --tw-ring-offset-width: 2px; + --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color) + } + + .focus\:outline-none:focus { + --tw-outline-style: none; + outline-style: none + } + + .focus-visible\:ring-1:focus-visible { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .focus-visible\:ring-2:focus-visible { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .focus-visible\:ring-ring:focus-visible { + --tw-ring-color: var(--ring) + } + + .focus-visible\:ring-offset-1:focus-visible { + --tw-ring-offset-width: 1px; + --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color) + } + + .focus-visible\:ring-offset-2:focus-visible { + --tw-ring-offset-width: 2px; + --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color) + } + + .focus-visible\:ring-offset-background:focus-visible { + --tw-ring-offset-color: var(--background) + } + + .focus-visible\:outline-none:focus-visible { + --tw-outline-style: none; + outline-style: none + } + + .active\:scale-95:active { + --tw-scale-x: 95%; + --tw-scale-y: 95%; + --tw-scale-z: 95%; + scale: var(--tw-scale-x) var(--tw-scale-y) + } + + .disabled\:pointer-events-none:disabled { + pointer-events: none + } + + .disabled\:cursor-not-allowed:disabled { + cursor: not-allowed + } + + .disabled\:opacity-50:disabled { + opacity: .5 + } + + .has-focus\:border-ring:has(:focus) { + border-color: var(--ring) + } + + .has-focus\:ring-\[3px\]:has(:focus) { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .has-focus\:ring-ring\/50:has(:focus) { + --tw-ring-color: var(--ring) + } + + @supports (color:color-mix(in lab, red, red)) { + .has-focus\:ring-ring\/50:has(:focus) { + --tw-ring-color: color-mix(in oklab, var(--ring) 50%, transparent) + } + }.has-\[\:disabled\]\:opacity-50:has(:disabled) { + opacity: .5 + } + + .aria-disabled\:pointer-events-none[aria-disabled=true] { + pointer-events: none + } + + .aria-disabled\:opacity-50[aria-disabled=true] { + opacity: .5 + } + + .aria-selected\:text-muted-foreground[aria-selected=true] { + color: var(--muted-foreground) + } + + .data-\[active\=true\]\:font-medium[data-active=true] { + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium) + } + + .data-\[disabled\]\:pointer-events-none[data-disabled] { + pointer-events: none + } + + .data-\[disabled\]\:opacity-50[data-disabled] { + opacity: .5 + } + + .data-\[disabled\=true\]\:pointer-events-none[data-disabled=true] { + pointer-events: none + } + + .data-\[disabled\=true\]\:opacity-50[data-disabled=true] { + opacity: .5 + } + + .data-\[motion\=from-end\]\:slide-in-from-right-52[data-motion=from-end] { + --tw-enter-translate-x: calc(52 * var(--spacing)) + } + + .data-\[motion\=from-start\]\:slide-in-from-left-52[data-motion=from-start] { + --tw-enter-translate-x: calc(52 * var(--spacing) * -1) + } + + .data-\[motion\=to-end\]\:slide-out-to-right-52[data-motion=to-end] { + --tw-exit-translate-x: calc(52 * var(--spacing)) + } + + .data-\[motion\=to-start\]\:slide-out-to-left-52[data-motion=to-start] { + --tw-exit-translate-x: calc(52 * var(--spacing) * -1) + } + + .data-\[motion\^\=from-\]\:animate-in[data-motion^=from-] { + animation: enter var(--tw-animation-duration, var(--tw-duration, .15s)) var(--tw-ease, ease) var(--tw-animation-delay, 0s) var(--tw-animation-iteration-count, 1) var(--tw-animation-direction, normal) var(--tw-animation-fill-mode, none) + } + + .data-\[motion\^\=from-\]\:fade-in[data-motion^=from-] { + --tw-enter-opacity: 0 + } + + .data-\[motion\^\=to-\]\:animate-out[data-motion^=to-] { + animation: exit var(--tw-animation-duration, var(--tw-duration, .15s)) var(--tw-ease, ease) var(--tw-animation-delay, 0s) var(--tw-animation-iteration-count, 1) var(--tw-animation-direction, normal) var(--tw-animation-fill-mode, none) + } + + .data-\[motion\^\=to-\]\:fade-out[data-motion^=to-] { + --tw-exit-opacity: 0 + } + + .data-\[panel-group-direction\=vertical\]\:h-px[data-panel-group-direction=vertical] { + height: 1px + } + + .data-\[panel-group-direction\=vertical\]\:w-full[data-panel-group-direction=vertical] { + width: 100% + } + + .data-\[panel-group-direction\=vertical\]\:flex-col[data-panel-group-direction=vertical] { + flex-direction: column + } + + .data-\[panel-group-direction\=vertical\]\:after\:left-0[data-panel-group-direction=vertical]:after { + content: var(--tw-content); + left: 0 + } + + .data-\[panel-group-direction\=vertical\]\:after\:h-1[data-panel-group-direction=vertical]:after { + content: var(--tw-content); + height: var(--spacing) + } + + .data-\[panel-group-direction\=vertical\]\:after\:w-full[data-panel-group-direction=vertical]:after { + content: var(--tw-content); + width: 100% + } + + .data-\[panel-group-direction\=vertical\]\:after\:translate-x-0[data-panel-group-direction=vertical]:after { + content: var(--tw-content); + --tw-translate-x: 0px; + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .data-\[panel-group-direction\=vertical\]\:after\:-translate-y-1\/2[data-panel-group-direction=vertical]:after { + content: var(--tw-content); + --tw-translate-y: calc(calc(1 / 2 * 100%) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .data-\[placeholder\]\:text-muted-foreground[data-placeholder] { + color: var(--muted-foreground) + } + + .data-\[range-end\=true\]\:rounded-md[data-range-end=true] { + border-radius: calc(var(--radius) - 2px) + } + + .data-\[range-end\=true\]\:bg-primary[data-range-end=true] { + background-color: var(--primary) + } + + .data-\[range-end\=true\]\:text-primary-foreground[data-range-end=true] { + color: var(--primary-foreground) + } + + .data-\[range-middle\=true\]\:rounded-none[data-range-middle=true] { + border-radius: 0 + } + + .data-\[range-middle\=true\]\:bg-accent[data-range-middle=true] { + background-color: var(--accent) + } + + .data-\[range-middle\=true\]\:text-accent-foreground[data-range-middle=true] { + color: var(--accent-foreground) + } + + .data-\[range-start\=true\]\:rounded-md[data-range-start=true] { + border-radius: calc(var(--radius) - 2px) + } + + .data-\[range-start\=true\]\:bg-primary[data-range-start=true] { + background-color: var(--primary) + } + + .data-\[range-start\=true\]\:text-primary-foreground[data-range-start=true] { + color: var(--primary-foreground) + } + + .data-\[selected-single\=true\]\:bg-primary[data-selected-single=true] { + background-color: var(--primary) + } + + .data-\[selected-single\=true\]\:text-primary-foreground[data-selected-single=true] { + color: var(--primary-foreground) + } + + .data-\[selected\=true\]\:rounded-none[data-selected=true] { + border-radius: 0 + } + + .data-\[selected\=true\]\:bg-accent[data-selected=true] { + background-color: var(--accent) + } + + .data-\[selected\=true\]\:text-accent-foreground[data-selected=true] { + color: var(--accent-foreground) + } + + .data-\[side\=bottom\]\:translate-y-1[data-side=bottom] { + --tw-translate-y: var(--spacing); + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom] { + --tw-enter-translate-y: calc(2 * var(--spacing) * -1) + } + + .data-\[side\=left\]\:-translate-x-1[data-side=left] { + --tw-translate-x: calc(var(--spacing) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .data-\[side\=left\]\:slide-in-from-right-2[data-side=left] { + --tw-enter-translate-x: calc(2 * var(--spacing)) + } + + .data-\[side\=right\]\:translate-x-1[data-side=right] { + --tw-translate-x: var(--spacing); + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .data-\[side\=right\]\:slide-in-from-left-2[data-side=right] { + --tw-enter-translate-x: calc(2 * var(--spacing) * -1) + } + + .data-\[side\=top\]\:-translate-y-1[data-side=top] { + --tw-translate-y: calc(var(--spacing) * -1); + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top] { + --tw-enter-translate-y: calc(2 * var(--spacing)) + } + + .data-\[state\=active\]\:bg-background[data-state=active] { + background-color: var(--background) + } + + .data-\[state\=active\]\:text-foreground[data-state=active] { + color: var(--foreground) + } + + .data-\[state\=active\]\:shadow[data-state=active] { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, #0000001a), 0 1px 2px -1px var(--tw-shadow-color, #0000001a); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .data-\[state\=checked\]\:translate-x-4[data-state=checked] { + --tw-translate-x: calc(var(--spacing) * 4); + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .data-\[state\=checked\]\:bg-primary[data-state=checked] { + background-color: var(--primary) + } + + .data-\[state\=checked\]\:text-primary-foreground[data-state=checked] { + color: var(--primary-foreground) + } + + .data-\[state\=closed\]\:animate-accordion-up[data-state=closed] { + animation: accordion-up var(--tw-animation-duration, var(--tw-duration, .2s)) var(--tw-ease, ease-out) var(--tw-animation-delay, 0s) var(--tw-animation-iteration-count, 1) var(--tw-animation-direction, normal) var(--tw-animation-fill-mode, none) + } + + .data-\[state\=closed\]\:animate-out[data-state=closed] { + animation: exit var(--tw-animation-duration, var(--tw-duration, .15s)) var(--tw-ease, ease) var(--tw-animation-delay, 0s) var(--tw-animation-iteration-count, 1) var(--tw-animation-direction, normal) var(--tw-animation-fill-mode, none) + } + + .data-\[state\=closed\]\:duration-300[data-state=closed] { + --tw-duration: .3s; + transition-duration: .3s + } + + .data-\[state\=closed\]\:fade-out-0[data-state=closed] { + --tw-exit-opacity: 0 + } + + .data-\[state\=closed\]\:zoom-out-95[data-state=closed] { + --tw-exit-scale: .95 + } + + .data-\[state\=closed\]\:slide-out-to-bottom[data-state=closed] { + --tw-exit-translate-y: 100% + } + + .data-\[state\=closed\]\:slide-out-to-left[data-state=closed] { + --tw-exit-translate-x: -100% + } + + .data-\[state\=closed\]\:slide-out-to-right[data-state=closed] { + --tw-exit-translate-x: 100% + } + + .data-\[state\=closed\]\:slide-out-to-top[data-state=closed] { + --tw-exit-translate-y: -100% + } + + .data-\[state\=hidden\]\:animate-out[data-state=hidden] { + animation: exit var(--tw-animation-duration, var(--tw-duration, .15s)) var(--tw-ease, ease) var(--tw-animation-delay, 0s) var(--tw-animation-iteration-count, 1) var(--tw-animation-direction, normal) var(--tw-animation-fill-mode, none) + } + + .data-\[state\=hidden\]\:fade-out[data-state=hidden] { + --tw-exit-opacity: 0 + } + + .data-\[state\=on\]\:bg-accent[data-state=on] { + background-color: var(--accent) + } + + .data-\[state\=on\]\:text-accent-foreground[data-state=on] { + color: var(--accent-foreground) + } + + .data-\[state\=open\]\:animate-accordion-down[data-state=open] { + animation: accordion-down var(--tw-animation-duration, var(--tw-duration, .2s)) var(--tw-ease, ease-out) var(--tw-animation-delay, 0s) var(--tw-animation-iteration-count, 1) var(--tw-animation-direction, normal) var(--tw-animation-fill-mode, none) + } + + .data-\[state\=open\]\:animate-in[data-state=open] { + animation: enter var(--tw-animation-duration, var(--tw-duration, .15s)) var(--tw-ease, ease) var(--tw-animation-delay, 0s) var(--tw-animation-iteration-count, 1) var(--tw-animation-direction, normal) var(--tw-animation-fill-mode, none) + } + + .data-\[state\=open\]\:bg-accent[data-state=open], .data-\[state\=open\]\:bg-accent\/50[data-state=open] { + background-color: var(--accent) + } + + @supports (color:color-mix(in lab, red, red)) { + .data-\[state\=open\]\:bg-accent\/50[data-state=open] { + background-color: color-mix(in oklab, var(--accent) 50%, transparent) + } + }.data-\[state\=open\]\:bg-secondary[data-state=open] { + background-color: var(--secondary) + } + + .data-\[state\=open\]\:text-accent-foreground[data-state=open] { + color: var(--accent-foreground) + } + + .data-\[state\=open\]\:text-muted-foreground[data-state=open] { + color: var(--muted-foreground) + } + + .data-\[state\=open\]\:opacity-100[data-state=open] { + opacity: 1 + } + + .data-\[state\=open\]\:duration-500[data-state=open] { + --tw-duration: .5s; + transition-duration: .5s + } + + .data-\[state\=open\]\:fade-in-0[data-state=open] { + --tw-enter-opacity: 0 + } + + .data-\[state\=open\]\:zoom-in-90[data-state=open] { + --tw-enter-scale: .9 + } + + .data-\[state\=open\]\:zoom-in-95[data-state=open] { + --tw-enter-scale: .95 + } + + .data-\[state\=open\]\:slide-in-from-bottom[data-state=open] { + --tw-enter-translate-y: 100% + } + + .data-\[state\=open\]\:slide-in-from-left[data-state=open] { + --tw-enter-translate-x: -100% + } + + .data-\[state\=open\]\:slide-in-from-right[data-state=open] { + --tw-enter-translate-x: 100% + } + + .data-\[state\=open\]\:slide-in-from-top[data-state=open] { + --tw-enter-translate-y: -100% + } + + @media (hover: hover) { + .data-\[state\=open\]\:hover\:bg-accent[data-state=open]:hover { + background-color: var(--accent) + } + }.data-\[state\=open\]\:focus\:bg-accent[data-state=open]:focus { + background-color: var(--accent) + } + + .data-\[state\=selected\]\:bg-muted[data-state=selected] { + background-color: var(--muted) + } + + .data-\[state\=unchecked\]\:translate-x-0[data-state=unchecked] { + --tw-translate-x: 0px; + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .data-\[state\=unchecked\]\:bg-input[data-state=unchecked] { + background-color: var(--input) + } + + .data-\[state\=visible\]\:animate-in[data-state=visible] { + animation: enter var(--tw-animation-duration, var(--tw-duration, .15s)) var(--tw-ease, ease) var(--tw-animation-delay, 0s) var(--tw-animation-iteration-count, 1) var(--tw-animation-direction, normal) var(--tw-animation-fill-mode, none) + } + + .data-\[state\=visible\]\:fade-in[data-state=visible] { + --tw-enter-opacity: 0 + } + + @media (width >= 40rem) { + .sm\:mt-0 { + margin-top: 0 + } + + .sm\:flex { + display: flex + } + + .sm\:inline { + display: inline + } + + .sm\:inline-flex { + display: inline-flex + } + + .sm\:h-36 { + height: calc(var(--spacing) * 36) + } + + .sm\:max-w-sm { + max-width: var(--container-sm) + } + + .sm\:grid-cols-2 { + grid-template-columns:repeat(2, minmax(0, 1fr)) + } + + .sm\:flex-row { + flex-direction: row + } + + .sm\:justify-end { + justify-content: flex-end + } + + .sm\:gap-2\.5 { + gap: calc(var(--spacing) * 2.5) + } + + :where(.sm\:space-x-2>:not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse))) + } + + .sm\:rounded-lg { + border-radius: var(--radius) + } + + .sm\:px-4 { + padding-inline: calc(var(--spacing) * 4) + } + + .sm\:px-5 { + padding-inline: calc(var(--spacing) * 5) + } + + .sm\:text-left { + text-align: left + } + + .sm\:text-4xl { + font-size: var(--text-4xl); + line-height: var(--tw-leading, var(--text-4xl--line-height)) + } + + .sm\:text-6xl { + font-size: var(--text-6xl); + line-height: var(--tw-leading, var(--text-6xl--line-height)) + } + }@media (width >= 48rem) { + .md\:absolute { + position: absolute + } + + .md\:block { + display: block + } + + .md\:flex { + display: flex + } + + .md\:hidden { + display: none + } + + .md\:w-\[var\(--radix-navigation-menu-viewport-width\)\] { + width: var(--radix-navigation-menu-viewport-width) + } + + .md\:w-auto { + width: auto + } + + .md\:flex-row { + flex-direction: row + } + + .md\:text-sm { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)) + } + + .md\:opacity-0 { + opacity: 0 + } + + .md\:peer-data-\[variant\=inset\]\:m-2:is(:where(.peer)[data-variant=inset]~*) { + margin: calc(var(--spacing) * 2) + } + + .md\:peer-data-\[variant\=inset\]\:ml-0:is(:where(.peer)[data-variant=inset]~*) { + margin-left: 0 + } + + .md\:peer-data-\[variant\=inset\]\:rounded-xl:is(:where(.peer)[data-variant=inset]~*) { + border-radius: calc(var(--radius) + 4px) + } + + .md\:peer-data-\[variant\=inset\]\:shadow:is(:where(.peer)[data-variant=inset]~*) { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, #0000001a), 0 1px 2px -1px var(--tw-shadow-color, #0000001a); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) + } + + .md\:peer-data-\[state\=collapsed\]\:peer-data-\[variant\=inset\]\:ml-2:is(:where(.peer)[data-state=collapsed]~*):is(:where(.peer)[data-variant=inset]~*) { + margin-left: calc(var(--spacing) * 2) + } +}.after\:md\:hidden:after { + content: var(--tw-content) + } + + @media (width >= 48rem) { + .after\:md\:hidden:after { + display: none + } + }@media (width >= 64rem) { + .lg\:order-1 { + order: 1 + } + + .lg\:order-2 { + order: 2 + } + + .lg\:grid-cols-2 { + grid-template-columns:repeat(2, minmax(0, 1fr)) + } + + .lg\:grid-cols-4 { + grid-template-columns:repeat(4, minmax(0, 1fr)) + } + + .lg\:items-center { + align-items: center + } + + .lg\:p-10 { + padding: calc(var(--spacing) * 10) + } + + .lg\:text-left { + text-align: left + } +}.dark\:border-destructive:is(.dark *) { + border-color: var(--destructive) + } + + .\[\&_\.recharts-cartesian-axis-tick_text\]\:fill-muted-foreground .recharts-cartesian-axis-tick text { + fill: var(--muted-foreground) + } + + .\[\&_\.recharts-cartesian-grid_line\[stroke\=\'\#ccc\'\]\]\:stroke-border\/50 .recharts-cartesian-grid line[stroke=\#ccc] { + stroke: var(--border) + } + + @supports (color:color-mix(in lab, red, red)) { + .\[\&_\.recharts-cartesian-grid_line\[stroke\=\'\#ccc\'\]\]\:stroke-border\/50 .recharts-cartesian-grid line[stroke=\#ccc] { + stroke: color-mix(in oklab, var(--border) 50%, transparent) + } + }.\[\&_\.recharts-curve\.recharts-tooltip-cursor\]\:stroke-border .recharts-curve.recharts-tooltip-cursor { + stroke: var(--border) + } + + .\[\&_\.recharts-dot\[stroke\=\'\#fff\'\]\]\:stroke-transparent .recharts-dot[stroke=\#fff] { + stroke: #0000 + } + + .\[\&_\.recharts-layer\]\:outline-none .recharts-layer { + --tw-outline-style: none; + outline-style: none + } + + .\[\&_\.recharts-polar-grid_\[stroke\=\'\#ccc\'\]\]\:stroke-border .recharts-polar-grid [stroke=\#ccc] { + stroke: var(--border) + } + + .\[\&_\.recharts-radial-bar-background-sector\]\:fill-muted .recharts-radial-bar-background-sector, .\[\&_\.recharts-rectangle\.recharts-tooltip-cursor\]\:fill-muted .recharts-rectangle.recharts-tooltip-cursor { + fill: var(--muted) + } + + .\[\&_\.recharts-reference-line_\[stroke\=\'\#ccc\'\]\]\:stroke-border .recharts-reference-line [stroke=\#ccc] { + stroke: var(--border) + } + + .\[\&_\.recharts-sector\]\:outline-none .recharts-sector { + --tw-outline-style: none; + outline-style: none + } + + .\[\&_\.recharts-sector\[stroke\=\'\#fff\'\]\]\:stroke-transparent .recharts-sector[stroke=\#fff] { + stroke: #0000 + } + + .\[\&_\.recharts-surface\]\:outline-none .recharts-surface { + --tw-outline-style: none; + outline-style: none + } + + .\[\&_\[cmdk-group-heading\]\]\:px-2 [cmdk-group-heading] { + padding-inline: calc(var(--spacing) * 2) + } + + .\[\&_\[cmdk-group-heading\]\]\:py-1\.5 [cmdk-group-heading] { + padding-block: calc(var(--spacing) * 1.5) + } + + .\[\&_\[cmdk-group-heading\]\]\:text-xs [cmdk-group-heading] { + font-size: var(--text-xs); + line-height: var(--tw-leading, var(--text-xs--line-height)) + } + + .\[\&_\[cmdk-group-heading\]\]\:font-medium [cmdk-group-heading] { + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium) + } + + .\[\&_\[cmdk-group-heading\]\]\:text-muted-foreground [cmdk-group-heading] { + color: var(--muted-foreground) + } + + .\[\&_\[cmdk-group\]\]\:px-2 [cmdk-group] { + padding-inline: calc(var(--spacing) * 2) + } + + .\[\&_\[cmdk-group\]\:not\(\[hidden\]\)_\~\[cmdk-group\]\]\:pt-0 [cmdk-group]:not([hidden]) ~ [cmdk-group] { + padding-top: 0 + } + + .\[\&_\[cmdk-input-wrapper\]_svg\]\:h-5 [cmdk-input-wrapper] svg { + height: calc(var(--spacing) * 5) + } + + .\[\&_\[cmdk-input-wrapper\]_svg\]\:w-5 [cmdk-input-wrapper] svg { + width: calc(var(--spacing) * 5) + } + + .\[\&_\[cmdk-input\]\]\:h-12 [cmdk-input] { + height: calc(var(--spacing) * 12) + } + + .\[\&_\[cmdk-item\]\]\:px-2 [cmdk-item] { + padding-inline: calc(var(--spacing) * 2) + } + + .\[\&_\[cmdk-item\]\]\:py-3 [cmdk-item] { + padding-block: calc(var(--spacing) * 3) + } + + .\[\&_\[cmdk-item\]_svg\]\:h-5 [cmdk-item] svg { + height: calc(var(--spacing) * 5) + } + + .\[\&_\[cmdk-item\]_svg\]\:w-5 [cmdk-item] svg { + width: calc(var(--spacing) * 5) + } + + .\[\&_p\]\:leading-relaxed p { + --tw-leading: var(--leading-relaxed); + line-height: var(--leading-relaxed) + } + + .\[\&_svg\]\:pointer-events-none svg { + pointer-events: none + } + + .\[\&_svg\]\:size-4 svg { + width: calc(var(--spacing) * 4); + height: calc(var(--spacing) * 4) + } + + .\[\&_svg\]\:shrink-0 svg { + flex-shrink: 0 + } + + .\[\&_tr\]\:border-b tr { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 1px + } + + .\[\&_tr\:last-child\]\:border-0 tr:last-child { + border-style: var(--tw-border-style); + border-width: 0 + } + + .\[\&\:first-child\[data-selected\=true\]_button\]\:rounded-l-md:first-child[data-selected=true] button { + border-top-left-radius: calc(var(--radius) - 2px); + border-bottom-left-radius: calc(var(--radius) - 2px) + } + + .\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]) { + padding-right: 0 + } + + :is(.rtl\:\*\*\:\[\.rdp-button\\_next\>svg\]\:rotate-180:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *) *):is(.rdp-button_next>svg), :is(.rtl\:\*\*\:\[\.rdp-button\\_previous\>svg\]\:rotate-180:where(:is(:lang(ae),:lang(ar),:lang(arc),:lang(bcc),:lang(bqi),:lang(ckb),:lang(dv),:lang(fa),:lang(glk),:lang(he),:lang(ku),:lang(mzn),:lang(nqo),:lang(pnb),:lang(ps),:lang(sd),:lang(ug),:lang(ur),:lang(yi)),[dir=rtl],[dir=rtl] *) *):is(.rdp-button_previous>svg) { + rotate: 180deg + } + + .\[\&\:last-child\[data-selected\=true\]_button\]\:rounded-r-md:last-child[data-selected=true] button { + border-top-right-radius: calc(var(--radius) - 2px); + border-bottom-right-radius: calc(var(--radius) - 2px) + } + + .\[\&\>\[role\=checkbox\]\]\:translate-y-\[2px\] > [role=checkbox] { + --tw-translate-y: 2px; + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .\[\&\>button\]\:hidden > button { + display: none + } + + .\[\&\>span\]\:line-clamp-1 > span { + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + display: -webkit-box; + overflow: hidden + } + + .\[\&\>span\]\:text-xs > span { + font-size: var(--text-xs); + line-height: var(--tw-leading, var(--text-xs--line-height)) + } + + .\[\&\>span\]\:opacity-70 > span { + opacity: .7 + } + + .\[\&\>span\:last-child\]\:truncate > span:last-child { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden + } + + .\[\&\>svg\]\:absolute > svg { + position: absolute + } + + .\[\&\>svg\]\:top-4 > svg { + top: calc(var(--spacing) * 4) + } + + .\[\&\>svg\]\:left-4 > svg { + left: calc(var(--spacing) * 4) + } + + .\[\&\>svg\]\:size-3\.5 > svg { + width: calc(var(--spacing) * 3.5); + height: calc(var(--spacing) * 3.5) + } + + .\[\&\>svg\]\:size-4 > svg { + width: calc(var(--spacing) * 4); + height: calc(var(--spacing) * 4) + } + + .\[\&\>svg\]\:h-2\.5 > svg { + height: calc(var(--spacing) * 2.5) + } + + .\[\&\>svg\]\:h-3 > svg { + height: calc(var(--spacing) * 3) + } + + .\[\&\>svg\]\:h-3\.5 > svg { + height: calc(var(--spacing) * 3.5) + } + + .\[\&\>svg\]\:w-2\.5 > svg { + width: calc(var(--spacing) * 2.5) + } + + .\[\&\>svg\]\:w-3 > svg { + width: calc(var(--spacing) * 3) + } + + .\[\&\>svg\]\:w-3\.5 > svg { + width: calc(var(--spacing) * 3.5) + } + + .\[\&\>svg\]\:shrink-0 > svg { + flex-shrink: 0 + } + + .\[\&\>svg\]\:text-destructive > svg { + color: var(--destructive) + } + + .\[\&\>svg\]\:text-foreground > svg { + color: var(--foreground) + } + + .\[\&\>svg\]\:text-muted-foreground > svg { + color: var(--muted-foreground) + } + + .\[\&\>svg\+div\]\:translate-y-\[-3px\] > svg + div { + --tw-translate-y: -3px; + translate: var(--tw-translate-x) var(--tw-translate-y) + } + + .\[\&\>svg\~\*\]\:pl-7 > svg ~ * { + padding-left: calc(var(--spacing) * 7) + } + + .\[\&\>tr\]\:last\:border-b-0 > tr:last-child { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 0 + } + + .\[\&\[data-panel-group-direction\=vertical\]\>div\]\:rotate-90[data-panel-group-direction=vertical] > div { + rotate: 90deg + } + + .\[\&\[data-state\=open\]\>svg\]\:rotate-180[data-state=open] > svg { + rotate: 180deg + } + + [data-side=left] .\[\[data-side\=left\]_\&\]\:cursor-w-resize { + cursor: w-resize + } + + [data-side=left][data-collapsible=offcanvas] .\[\[data-side\=left\]\[data-collapsible\=offcanvas\]_\&\]\:-right-2 { + right: calc(var(--spacing) * -2) + } + + [data-side=left][data-state=collapsed] .\[\[data-side\=left\]\[data-state\=collapsed\]_\&\]\:cursor-e-resize, [data-side=right] .\[\[data-side\=right\]_\&\]\:cursor-e-resize { + cursor: e-resize + } + + [data-side=right][data-collapsible=offcanvas] .\[\[data-side\=right\]\[data-collapsible\=offcanvas\]_\&\]\:-left-2 { + left: calc(var(--spacing) * -2) + } + + [data-side=right][data-state=collapsed] .\[\[data-side\=right\]\[data-state\=collapsed\]_\&\]\:cursor-w-resize { + cursor: w-resize + } + + [data-slot=card-content] .\[\[data-slot\=card-content\]_\&\]\:bg-transparent, [data-slot=popover-content] .\[\[data-slot\=popover-content\]_\&\]\:bg-transparent { + background-color: #0000 + } +} + +@property --tw-animation-delay { + syntax: "*"; + inherits: false; + initial-value: 0s +} + +@property --tw-animation-direction { + syntax: "*"; + inherits: false; + initial-value: normal +} + +@property --tw-animation-duration { + syntax: "*"; + inherits: false +} + +@property --tw-animation-fill-mode { + syntax: "*"; + inherits: false; + initial-value: none +} + +@property --tw-animation-iteration-count { + syntax: "*"; + inherits: false; + initial-value: 1 +} + +@property --tw-enter-blur { + syntax: "*"; + inherits: false; + initial-value: 0 +} + +@property --tw-enter-opacity { + syntax: "*"; + inherits: false; + initial-value: 1 +} + +@property --tw-enter-rotate { + syntax: "*"; + inherits: false; + initial-value: 0 +} + +@property --tw-enter-scale { + syntax: "*"; + inherits: false; + initial-value: 1 +} + +@property --tw-enter-translate-x { + syntax: "*"; + inherits: false; + initial-value: 0 +} + +@property --tw-enter-translate-y { + syntax: "*"; + inherits: false; + initial-value: 0 +} + +@property --tw-exit-blur { + syntax: "*"; + inherits: false; + initial-value: 0 +} + +@property --tw-exit-opacity { + syntax: "*"; + inherits: false; + initial-value: 1 +} + +@property --tw-exit-rotate { + syntax: "*"; + inherits: false; + initial-value: 0 +} + +@property --tw-exit-scale { + syntax: "*"; + inherits: false; + initial-value: 1 +} + +@property --tw-exit-translate-x { + syntax: "*"; + inherits: false; + initial-value: 0 +} + +@property --tw-exit-translate-y { + syntax: "*"; + inherits: false; + initial-value: 0 +} + +@font-face { + font-family: Onest; + src: url(/__l5e/assets-v1/16a891b8-0750-41ef-a9f6-95a83cb89458/Onest-VariableFont_wght.ttf) format("truetype"); + font-weight: 100 800; + font-display: swap +} + +@font-face { + font-family: Syne; + src: url(/__l5e/assets-v1/45907748-7e1c-47da-9513-dba7dc3e186a/Syne-VariableFont_wght.ttf) format("truetype"); + font-weight: 400 800; + font-display: swap +} + +:root { + --radius: .875rem; + --font-heading: "Hanken Grotesk", system-ui, sans-serif; + --font-body: "Hanken Grotesk", system-ui, sans-serif; + --font-code: "JetBrains Mono", ui-monospace, monospace; + --background: oklch(16.8% .004 285); + --foreground: oklch(97.3% .003 285); + --card: oklch(21.2% .005 285); + --card-foreground: oklch(97.3% .003 285); + --popover: oklch(21.2% .005 285); + --popover-foreground: oklch(97.3% .003 285); + --primary: #db486f; + --primary-foreground: #0f0f12; + --brand-mid: #e67189; + --brand-soft: #ef9dab; + --brand-text: var(--brand-soft); + --secondary: oklch(25.3% .005 285); + --secondary-foreground: oklch(97.3% .003 285); + --muted: oklch(25.3% .005 285); + --muted-foreground: oklch(70.5% .006 285); + --accent: oklch(28% .02 8.5); + --accent-foreground: oklch(97.3% .003 285); + --destructive: oklch(63% .2 25); + --destructive-foreground: oklch(99% 0 0); + --border: oklch(100% 0 0/.1); + --input: oklch(100% 0 0/.14); + --ring: #db486f; + --code-bg: oklch(20.1% .004 285); + --code-comment: oklch(66% .09 160); + --code-keyword: var(--brand-mid); + --code-string: oklch(80% .11 90); + --code-fn: oklch(80% .09 240); + --gradient-brand: linear-gradient(100deg, var(--primary), var(--brand-mid) 55%, var(--brand-soft)); + --gradient-halo: radial-gradient(60% 60% at 50% 0%, var(--primary), transparent 70%) +} + +@supports (color:color-mix(in lab, red, red)) { + :root { + --gradient-halo: radial-gradient(60% 60% at 50% 0%, color-mix(in oklab, var(--primary) 22%, transparent), transparent 70%) + } +} + +:root { + --shadow-brand: 0 12px 40px -12px var(--primary) +} + +@supports (color:color-mix(in lab, red, red)) { + :root { + --shadow-brand: 0 12px 40px -12px color-mix(in oklab, var(--primary) 55%, transparent) + } +} + +:root { + --shadow-panel: 0 24px 70px -30px oklch(0% 0 0/.8) +} + +:root[data-theme="light"], +:root.light { + --background: oklch(98.5% .003 285); + --foreground: oklch(21.5% .006 285); + --card: oklch(100% 0 0); + --card-foreground: oklch(21.5% .006 285); + --popover: oklch(100% 0 0); + --popover-foreground: oklch(21.5% .006 285); + --primary: #db486f; + --primary-foreground: #0f0f12; + --brand-mid: #e67189; + --brand-soft: #ef9dab; + --brand-text: #c54164; + --secondary: oklch(95.5% .004 285); + --secondary-foreground: oklch(24% .006 285); + --muted: oklch(95.5% .004 285); + --muted-foreground: oklch(50.5% .008 285); + --accent: oklch(95.5% .018 8.5); + --accent-foreground: oklch(24% .006 285); + --border: oklch(20% .01 285/.12); + --input: oklch(20% .01 285/.18); + --code-bg: oklch(96.8% .004 285); + --code-comment: oklch(52% .08 160); + --code-keyword: #c54164; + --code-string: oklch(55% .11 90); + --code-fn: oklch(52% .12 240); + --gradient-halo: radial-gradient(60% 60% at 50% 0%, var(--primary), transparent 70%) +} + +@supports (color:color-mix(in lab, red, red)) { + :root.light { + --gradient-halo: radial-gradient(60% 60% at 50% 0%, color-mix(in oklab, var(--primary) 14%, transparent), transparent 70%) + } +} + +:root.light { + --shadow-panel: 0 24px 70px -34px oklch(20% .02 285/.22) +} + +:root.light .bg-grid { + background-image: linear-gradient(90deg, oklch(20% .01 285/.06) 1px, #0000 1px), linear-gradient(oklch(20% .01 285/.06) 1px, #0000 1px) +} + +:root.light .scroll-glow { + mix-blend-mode: normal; + opacity: .55 +} + +@keyframes halo-drift { + 0%, to { + transform: translate(-2%) translateY(-1%) scale(1) + } + 25% { + transform: translate(2%) translateY(1%) scale(1.05) + } + 50% { + transform: translate(3%) translateY(-2%) scale(1.08) + } + 75% { + transform: translate(-1%) translateY(2%) scale(1.04) + } +} + +html.braces-cursor-active, html.braces-cursor-active * { + cursor: none !important +} + +@keyframes logo-bar-drop { + 0% { + opacity: 0; + transform: translateY(-260px) + } + 70% { + opacity: 1; + transform: translateY(0) + } + 82% { + transform: translateY(-14px) + } + to { + opacity: 1; + transform: translateY(0) + } +} + +@keyframes logo-slide-left { + 0%, 38% { + transform: translate(46%) + } + to { + transform: translate(0) + } +} + +@keyframes logo-wordmark-in { + 0%, 42% { + opacity: 0; + clip-path: inset(0 100% 0 0) + } + 60% { + opacity: 1 + } + to { + opacity: 1; + clip-path: inset(0) + } +} + +@keyframes intro-symbol-out { + 0%, 62% { + opacity: 1; + transform: translateY(0) scale(1) + } + to { + opacity: 0; + transform: translateY(140px) scale(.94) + } +} + +@keyframes intro-overlay-out { + 0%, 74% { + opacity: 1 + } + to { + opacity: 0 + } +} + +.intro-splash { + animation: 2.1s ease-in-out both intro-overlay-out +} + +.intro-symbol { + animation: 2s cubic-bezier(.4, 0, .7, 1) both intro-symbol-out +} + +.intro-bar { + animation: .7s cubic-bezier(.34, 1.3, .64, 1) both logo-bar-drop +} + +@media (prefers-reduced-motion: reduce) { + .intro-splash, .intro-symbol, .intro-bar { + animation: none + } +} + +.scroll-glow { + z-index: 0; + mix-blend-mode: screen; + pointer-events: none; + --glow-x: 50%; + --glow-y: 15%; + background: radial-gradient(42vw 42vw at var(--glow-x) var(--glow-y), var(--primary), transparent 72%), radial-gradient(70vw 55vw at calc(100% - var(--glow-x)) calc(var(--glow-y) + 22%), var(--primary), transparent 72%); + position: fixed; + inset: 0 +} + +@supports (color:color-mix(in lab, red, red)) { + .scroll-glow { + background: radial-gradient(42vw 42vw at var(--glow-x) var(--glow-y), color-mix(in oklab, var(--primary) 26%, transparent), transparent 72%), radial-gradient(70vw 55vw at calc(100% - var(--glow-x)) calc(var(--glow-y) + 22%), color-mix(in oklab, var(--primary) 11%, transparent), transparent 72%) + } +} + +.scroll-glow { + filter: blur(40px) +} + +:root.light .logo-mono { + filter: invert() +} + +@property --tw-translate-x { + syntax: "*"; + inherits: false; + initial-value: 0 +} + +@property --tw-translate-y { + syntax: "*"; + inherits: false; + initial-value: 0 +} + +@property --tw-translate-z { + syntax: "*"; + inherits: false; + initial-value: 0 +} + +@property --tw-rotate-x { + syntax: "*"; + inherits: false +} + +@property --tw-rotate-y { + syntax: "*"; + inherits: false +} + +@property --tw-rotate-z { + syntax: "*"; + inherits: false +} + +@property --tw-skew-x { + syntax: "*"; + inherits: false +} + +@property --tw-skew-y { + syntax: "*"; + inherits: false +} + +@property --tw-space-y-reverse { + syntax: "*"; + inherits: false; + initial-value: 0 +} + +@property --tw-space-x-reverse { + syntax: "*"; + inherits: false; + initial-value: 0 +} + +@property --tw-border-style { + syntax: "*"; + inherits: false; + initial-value: solid +} + +@property --tw-leading { + syntax: "*"; + inherits: false +} + +@property --tw-font-weight { + syntax: "*"; + inherits: false +} + +@property --tw-tracking { + syntax: "*"; + inherits: false +} + +@property --tw-ordinal { + syntax: "*"; + inherits: false +} + +@property --tw-slashed-zero { + syntax: "*"; + inherits: false +} + +@property --tw-numeric-figure { + syntax: "*"; + inherits: false +} + +@property --tw-numeric-spacing { + syntax: "*"; + inherits: false +} + +@property --tw-numeric-fraction { + syntax: "*"; + inherits: false +} + +@property --tw-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000 +} + +@property --tw-shadow-color { + syntax: "*"; + inherits: false +} + +@property --tw-shadow-alpha { + syntax: "<percentage>"; + inherits: false; + initial-value: 100% +} + +@property --tw-inset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000 +} + +@property --tw-inset-shadow-color { + syntax: "*"; + inherits: false +} + +@property --tw-inset-shadow-alpha { + syntax: "<percentage>"; + inherits: false; + initial-value: 100% +} + +@property --tw-ring-color { + syntax: "*"; + inherits: false +} + +@property --tw-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000 +} + +@property --tw-inset-ring-color { + syntax: "*"; + inherits: false +} + +@property --tw-inset-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000 +} + +@property --tw-ring-inset { + syntax: "*"; + inherits: false +} + +@property --tw-ring-offset-width { + syntax: "<length>"; + inherits: false; + initial-value: 0 +} + +@property --tw-ring-offset-color { + syntax: "*"; + inherits: false; + initial-value: #fff +} + +@property --tw-ring-offset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000 +} + +@property --tw-outline-style { + syntax: "*"; + inherits: false; + initial-value: solid +} + +@property --tw-blur { + syntax: "*"; + inherits: false +} + +@property --tw-brightness { + syntax: "*"; + inherits: false +} + +@property --tw-contrast { + syntax: "*"; + inherits: false +} + +@property --tw-grayscale { + syntax: "*"; + inherits: false +} + +@property --tw-hue-rotate { + syntax: "*"; + inherits: false +} + +@property --tw-invert { + syntax: "*"; + inherits: false +} + +@property --tw-opacity { + syntax: "*"; + inherits: false +} + +@property --tw-saturate { + syntax: "*"; + inherits: false +} + +@property --tw-sepia { + syntax: "*"; + inherits: false +} + +@property --tw-drop-shadow { + syntax: "*"; + inherits: false +} + +@property --tw-drop-shadow-color { + syntax: "*"; + inherits: false +} + +@property --tw-drop-shadow-alpha { + syntax: "<percentage>"; + inherits: false; + initial-value: 100% +} + +@property --tw-drop-shadow-size { + syntax: "*"; + inherits: false +} + +@property --tw-backdrop-blur { + syntax: "*"; + inherits: false +} + +@property --tw-backdrop-brightness { + syntax: "*"; + inherits: false +} + +@property --tw-backdrop-contrast { + syntax: "*"; + inherits: false +} + +@property --tw-backdrop-grayscale { + syntax: "*"; + inherits: false +} + +@property --tw-backdrop-hue-rotate { + syntax: "*"; + inherits: false +} + +@property --tw-backdrop-invert { + syntax: "*"; + inherits: false +} + +@property --tw-backdrop-opacity { + syntax: "*"; + inherits: false +} + +@property --tw-backdrop-saturate { + syntax: "*"; + inherits: false +} + +@property --tw-backdrop-sepia { + syntax: "*"; + inherits: false +} + +@property --tw-duration { + syntax: "*"; + inherits: false +} + +@property --tw-ease { + syntax: "*"; + inherits: false +} + +@property --tw-content { + syntax: "*"; + inherits: false; + initial-value: "" +} + +@property --tw-scale-x { + syntax: "*"; + inherits: false; + initial-value: 1 +} + +@property --tw-scale-y { + syntax: "*"; + inherits: false; + initial-value: 1 +} + +@property --tw-scale-z { + syntax: "*"; + inherits: false; + initial-value: 1 +} + +@keyframes pulse { + 50% { + opacity: .5 + } +} + +@keyframes enter { + 0% { + opacity: var(--tw-enter-opacity, 1); + transform: translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0)); + filter: blur(var(--tw-enter-blur, 0)) + } +} + +@keyframes exit { + to { + opacity: var(--tw-exit-opacity, 1); + transform: translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0)); + filter: blur(var(--tw-exit-blur, 0)) + } +} + +@keyframes accordion-down { + 0% { + height: 0 + } + to { + height: var(--radix-accordion-content-height, var(--bits-accordion-content-height, var(--reka-accordion-content-height, var(--kb-accordion-content-height, var(--ngp-accordion-content-height, auto))))) + } +} + +@keyframes accordion-up { + 0% { + height: var(--radix-accordion-content-height, var(--bits-accordion-content-height, var(--reka-accordion-content-height, var(--kb-accordion-content-height, var(--ngp-accordion-content-height, auto))))) + } + to { + height: 0 + } +} + +@keyframes caret-blink { + 0%, 70%, to { + opacity: 1 + } + 20%, 50% { + opacity: 0 + } +} diff --git a/loveable.html b/loveable.html new file mode 100644 index 0000000..b17effc --- /dev/null +++ b/loveable.html @@ -0,0 +1,3262 @@ +<html lang="en" data-tsd-source="/src/routes/__root.tsx:113:5" class="braces-cursor-active dark" + style="color-scheme: dark;"> +<head data-tsd-source="/src/routes/__root.tsx:114:7"> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="preload" as="image" href="/__l5e/assets-v1/87ac2c2c-8740-41c3-9533-e52503d479ea/suites-logo.svg"> + <link rel="preload" as="image" href="/__l5e/assets-v1/4ccb5b2c-cd9a-4cb5-b285-4914c7be07f9/nestjs.png"> + <link rel="preload" as="image" href="/__l5e/assets-v1/cfba7528-07d7-43b3-bb9d-7f263a183b52/inversify.png"> + <link rel="preload" as="image" href="/__l5e/assets-v1/018d335e-b2c2-4cc4-a0c4-049cfb56fc8d/vitest.png"> + <link rel="preload" as="image" href="/__l5e/assets-v1/2c16c75b-903a-42f1-a3d4-4dc6a134fd71/jest.png"> + <link rel="preload" as="image" href="/__l5e/assets-v1/aeed902c-f1fe-4a95-b9bb-9af26f1eeceb/monday.png"> + <link rel="preload" as="image" href="/__l5e/assets-v1/c7a73c03-b232-4274-95e2-78d308900211/lemonade.png"> + <link rel="preload" as="image" href="/__l5e/assets-v1/b045d4a6-0fa9-4401-8035-4c777802d14c/balance.png"> + <link rel="preload" as="image" href="/__l5e/assets-v1/dd4e01f2-59e4-4945-9027-78b39d07d83b/harmonya.png"> + <link rel="stylesheet" + href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" + data-precedence="default"> + <link rel="stylesheet" href="/assets/styles-CsjCOkK4.css" data-precedence="default"> + <link rel="preload" as="image" href="/__l5e/assets-v1/258a194b-0c75-48e5-af31-5a56beb46312/vetric.png"> + <title>Suites — Unit Testing Framework for TypeScript Backends + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + +
+

A Unit Testing Framework For TypeScript + Backends Working With Inversion Of Control And Dependency Injection.

+
Get StartedView On Github
+
+
+
+

+ Fits right into your stack.

+

Use + Suites with the tools you already work with.

+
NestJSInversifyJSVitestJest+more
+
+
+
UserService.test.ts +
46 lines +
+ + +
+
+
+
+
1// Manual setup: every dependency mocked by hand2let userApi: jest.Mocked<UserApi>;3let database: jest.Mocked<Database>;4let logger: jest.Mocked<Logger>;5let userService: UserService;6​7beforeEach(() => {8  userApi = {9    getRandom: jest.fn(),10    findById: jest.fn(),11    // Missing methods? No compile-time checks.12  } as jest.Mocked<UserApi>;13​14  database = {15    saveUser: jest.fn(),16    deleteUser: jest.fn(),17    // What other methods exist? ¯\_(ツ)_/¯18  } as jest.Mocked<Database>;19​20  logger = {21    info: jest.fn(),22    error: jest.fn(),23  } as jest.Mocked<Logger>;24​25  // Manual wiring — repeated in every single test file26  userService = new UserService(27    userApi,28    database,29    logger,30  );31});32​33const { unit, unitRef } = await TestBed.solitary(UserService).compile();34​35it('should generate a random user', async () => {36  unitRef(UserApi).getRandom.mockResolvedValue({ id: 1 });37  await unit.generateRandomUser();38  expect(unitRef(Database).saveUser).toHaveBeenCalledWith(userFixture);39});40​41// And repeat all of the above for the next test…42it('should log a failure', async () => {43  userApi.getRandom.mockRejectedValue(error);44  await userService.generateRandomUser();45  expect(logger.error).toHaveBeenCalled();46});
+
+
manual mocks — maintained by hand
+
+
+
+
+
+

+ Declarative

+

+ Suites' declarative API creates fully-typed, isolated test environments with a single declaration. Suites + auto-generates all mocks and wires dependencies automatically.

+
+

+ Type-Safe

+

+ Generate type-safe mocks bound to implementations. Eliminate broken tests after refactors, silent runtime + failures, and manual type casting.

+

+ Refactoring Confidence

+

+ Change constructors, add dependencies, refactor classes — tests adapt automatically. Skip manual mock updates + and catch breaking changes at compile time, not runtime.

+
+

AI + Ready

+

One + canonical pattern teaches AI agents the entire API. Coding agents write correct tests in a single pass with + 95% less context consumption compared to manual mocking patterns.

+
+
+
+
+

+ Used By

+
monday.com logoLemonade logoba/ance logoHarmonya logovetric logo
+

Using Suites? Share your experience and help us shape the future + of Suites

+
+
+
+

Standardized Testing Across Teams

+

+ Stop relearning test patterns on every project. Suites provides a consistent, standardized approach that works + identically across NestJS, InversifyJS, and any DI framework, giving teams a unified testing experience.

+ See Framework Support →
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
04different inputs · one standard
+
+
+
+
+
+
+

+ Tests That Reveal Intent, Not Boilerplate

+

+ Suites' declarative API removes 90% of test setup code. No more scrolling through mock wiring, logic is front + and center. New team members write tests on day one, not day ten.

See Quick Start →
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
03layers removed · intent remains
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
02dependencies assembling
+
+
+

Type-Safe, Automatic Mocking

+

No + more debugging broken mocks. Suites automatically generates fully-typed mocks bound to implementation. Catch + errors at compile time, not runtime. Refactor with confidence while mocks stay valid when dependencies + change.

Learn about Mocking →
+
+
+
+
+

+ Built for AI-First Development

+

+ Manual mocking forces AI agents to hold 40+ lines of boilerplate per test in context. Suites provides one + canonical pattern that reduces token consumption by 95%. AI coding assistants like Claude Code, Cursor, and + GitHub Copilot generate accurate tests in a single pass without burning tokens on repetitive setup code.

+ Suites and AI →
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
01many patterns · one pattern
+
+
+
+

Released Under The MIT License

+

Copyright © 2025 · Omer + Morad

+
+ + + + + + + \ No newline at end of file diff --git a/netlify.toml b/netlify.toml index 681f152..6a9bd98 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,10 +1,10 @@ # =========================================== # BUILD # =========================================== -# Triggers an Algolia DocSearch crawler reindex after every successful -# production build. The script is a no-op for deploy previews and branch -# deploys, and a no-op (with a warning) if crawler env vars are missing, -# so it cannot break the build. +# Builds validate metadata, links, legacy fragments and emitted assets. +# The local plugin syncs the crawler configuration and requests a recrawl only +# after a production deploy is published. Previews never mutate the index. +# Production credentials are checked before building. # # Required env vars in Netlify → Site settings → Environment variables: # ALGOLIA_CRAWLER_USER_ID @@ -12,12 +12,15 @@ # ALGOLIA_CRAWLER_ID [build] -command = "yarn run build && yarn algolia:reindex" -publish = "build" +command = "pnpm build" +publish = "dist" + +[[plugins]] +package = "./plugins/algolia-crawler" # Pinned rather than left to the dashboard toggle, because file configuration # overrides UI configuration. Pretty URLs serves the slash-terminated form, -# which is what `trailingSlash: true` in docusaurus.config.ts declares as +# which is what `trailingSlash: "always"` in astro.config.mjs declares as # canonical. The two settings must agree or every page redirects away from # its own canonical URL. [build.processing.html] @@ -98,7 +101,7 @@ status = 301 [[redirects]] from = "/docs/getting-started/change-log" -to = "/docs/migration-guides/from-automock/" +to = "/docs/changelog/" status = 301 # =========================================== @@ -180,6 +183,17 @@ from = "/docs/developer-guide/*" to = "/docs/guides/" status = 301 +# =========================================== +# SITEMAP +# =========================================== +# Preserve the sitemap URL used by existing Search Console submissions. +# robots.txt advertises the generated sitemap-index.xml directly. + +[[redirects]] +from = "/sitemap.xml" +to = "/sitemap-index.xml" +status = 200 + # =========================================== # FAVICON / APPLE TOUCH ICONS # =========================================== @@ -217,11 +231,9 @@ from = "/api-reference" to = "/docs/api-reference/" status = 301 -[[redirects]] -from = "/docs" -to = "/docs/get-started/" -status = 301 - +# /docs is a real page on this site, not a redirect. Netlify skips a +# non-forced redirect when a file exists at the path, so a rule here would be +# dead weight: Pretty URLs sends /docs to /docs/ and the landing page answers. [[redirects]] from = "/docs/introduction/quick-example" @@ -262,7 +274,7 @@ status = 301 [[redirects]] from = "/docs/guides/adapters/inversifyjs" -to = "/docs/get-started/" +to = "/docs/guides/inversify/" status = 301 # =========================================== @@ -293,3 +305,9 @@ status = 301 from = "/docs/category/api-reference" to = "/docs/api-reference/" status = 301 + +# The guide now uses its reader-facing title as the canonical URL. +[[redirects]] +from = "/docs/guides/virtual-test-container" +to = "/docs/guides/how-suites-works/" +status = 301 diff --git a/package.json b/package.json index e13f175..1aac44d 100644 --- a/package.json +++ b/package.json @@ -1,59 +1,46 @@ { - "name": "suites.dev", + "name": "@suites/web", + "type": "module", "version": "0.0.0", "private": true, + "packageManager": "pnpm@9.15.0", "scripts": { - "docusaurus": "docusaurus", - "start": "docusaurus start", - "build": "docusaurus build", - "algolia:reindex": "node scripts/algolia-reindex.mjs", + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "postbuild": "node scripts/check-seo.mjs && node scripts/check-build.mjs", + "preview": "astro preview", + "astro": "astro", + "check": "astro check", + "typecheck": "astro check", + "check:seo": "node scripts/check-seo.mjs", + "check:build": "node scripts/check-build.mjs", + "test": "node --test tests/*.test.mjs", "algolia:sync-config": "node scripts/sync-algolia-config.mjs", - "swizzle": "docusaurus swizzle", - "deploy": "docusaurus deploy", - "clear": "docusaurus clear", - "serve": "docusaurus serve", - "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids", - "typecheck": "tsc" + "algolia:reindex": "node scripts/algolia-reindex.mjs" }, "dependencies": { - "@algolia/client-search": "^4.24.0", - "@docusaurus/core": "^3.4.0", - "@docusaurus/plugin-google-gtag": "^3.4.0", - "@docusaurus/plugin-sitemap": "^3.4.0", - "@docusaurus/preset-classic": "^3.4.0", - "@docusaurus/remark-plugin-npm2yarn": "^3.1.1", - "@docusaurus/theme-mermaid": "^3.4.0", - "@docusaurus/theme-search-algolia": "^3.4.0", - "@fortawesome/fontawesome-svg-core": "^6.5.2", - "@fortawesome/free-solid-svg-icons": "^6.5.2", - "@fortawesome/react-fontawesome": "^0.2.2", - "@mdx-js/react": "^3.0.0", - "clsx": "^2.1.0", - "prism-react-renderer": "^2.3.1", - "react": "^18.2.0", - "react-code-blocks": "^0.1.6", - "react-dom": "^18.2.0", - "react-icons": "^5.0.1", - "search-insights": "^2.15.0" + "@astrojs/check": "^0.9.4", + "@astrojs/mdx": "^4.0.0", + "@astrojs/react": "^4.0.0", + "@astrojs/sitemap": "^3.2.0", + "@docsearch/css": "^3.8.0", + "@docsearch/react": "^3.8.0", + "astro": "^5.0.0", + "astro-expressive-code": "^0.40.0", + "mermaid": "^11.4.0", + "react": "^18.3.0", + "react-dom": "^18.3.0", + "react-icons": "^5.4.0", + "remark-gfm": "^4.0.0", + "typescript": "^5.6.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.1.1", - "@docusaurus/tsconfig": "^3.1.1", - "@types/react": "16", - "typescript": "^5.2.2" + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0" }, - "browserslist": { - "production": [ - ">0.5%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "packageManager": "yarn@1.22.1+sha512.84bec57763ecbf87ce884fc98d550b0e0f8b7241b4425ec83d4879c160bfe1412be96f9e17e5551167d1bba43ba9b3a7aa4efea98ec25d84922c17c92321255c" + "engines": { + "node": ">=22", + "pnpm": ">=9" + } } diff --git a/plugins/algolia-crawler/index.js b/plugins/algolia-crawler/index.js new file mode 100644 index 0000000..0d2a7a0 --- /dev/null +++ b/plugins/algolia-crawler/index.js @@ -0,0 +1,16 @@ +import { syncCrawlerConfig } from '../../scripts/sync-algolia-config.mjs'; +import { reindexCrawler } from '../../scripts/algolia-reindex.mjs'; + +export function onPreBuild({ constants = {} } = {}) { + if (constants.IS_LOCAL || process.env.CONTEXT !== 'production') return; + const required = ['ALGOLIA_CRAWLER_USER_ID', 'ALGOLIA_CRAWLER_API_KEY', 'ALGOLIA_CRAWLER_ID']; + const missing = required.filter(name => !process.env[name]); + if (missing.length) throw new Error(`Set production build variables before publishing: ${missing.join(', ')}`); +} + +export async function onSuccess({ constants = {} } = {}) { + if (constants.IS_LOCAL || process.env.CONTEXT !== 'production') return; + // onSuccess runs after the deploy is live, so the crawler sees this version. + await syncCrawlerConfig(); + await reindexCrawler(); +} diff --git a/plugins/algolia-crawler/manifest.yml b/plugins/algolia-crawler/manifest.yml new file mode 100644 index 0000000..31a7032 --- /dev/null +++ b/plugins/algolia-crawler/manifest.yml @@ -0,0 +1 @@ +name: suites-algolia-crawler diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..a4ed115 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,6525 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@astrojs/check': + specifier: ^0.9.4 + version: 0.9.9(prettier@3.8.3)(typescript@5.9.3) + '@astrojs/mdx': + specifier: ^4.0.0 + version: 4.3.14(astro@5.18.1(@types/node@24.12.2)(@vercel/functions@2.2.13)(rollup@4.60.1)(typescript@5.9.3)(yaml@2.8.3)) + '@astrojs/react': + specifier: ^4.0.0 + version: 4.4.2(@types/node@24.12.2)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yaml@2.8.3) + '@astrojs/sitemap': + specifier: ^3.2.0 + version: 3.7.2 + '@docsearch/css': + specifier: ^3.8.0 + version: 3.9.0 + '@docsearch/react': + specifier: ^3.8.0 + version: 3.9.0(@algolia/client-search@5.54.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) + astro: + specifier: ^5.0.0 + version: 5.18.1(@types/node@24.12.2)(@vercel/functions@2.2.13)(rollup@4.60.1)(typescript@5.9.3)(yaml@2.8.3) + astro-expressive-code: + specifier: ^0.40.0 + version: 0.40.2(astro@5.18.1(@types/node@24.12.2)(@vercel/functions@2.2.13)(rollup@4.60.1)(typescript@5.9.3)(yaml@2.8.3)) + mermaid: + specifier: ^11.4.0 + version: 11.14.0 + react: + specifier: ^18.3.0 + version: 18.3.1 + react-dom: + specifier: ^18.3.0 + version: 18.3.1(react@18.3.1) + react-icons: + specifier: ^5.4.0 + version: 5.6.0(react@18.3.1) + remark-gfm: + specifier: ^4.0.0 + version: 4.0.1 + typescript: + specifier: ^5.6.0 + version: 5.9.3 + devDependencies: + '@types/react': + specifier: ^18.3.0 + version: 18.3.28 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.7(@types/react@18.3.28) + +packages: + + '@algolia/abtesting@1.20.0': + resolution: {integrity: sha512-5CqkS592H3+24b6H6CQ2RVpphdmAuIElZzv0Hngqo/ZEZpUZJ+KGLcueBhx33fv2wYBXyuuvskG5aQ7Ti+lR0g==} + engines: {node: '>= 14.0.0'} + + '@algolia/autocomplete-core@1.17.9': + resolution: {integrity: sha512-O7BxrpLDPJWWHv/DLA9DRFWs+iY1uOJZkqUwjS5HSZAGcl0hIVCQ97LTLewiZmZ402JYUrun+8NqFP+hCknlbQ==} + + '@algolia/autocomplete-plugin-algolia-insights@1.17.9': + resolution: {integrity: sha512-u1fEHkCbWF92DBeB/KHeMacsjsoI0wFhjZtlCq2ddZbAehshbZST6Hs0Avkc0s+4UyBGbMDnSuXHLuvRWK5iDQ==} + peerDependencies: + search-insights: '>= 1 < 3' + + '@algolia/autocomplete-preset-algolia@1.17.9': + resolution: {integrity: sha512-Na1OuceSJeg8j7ZWn5ssMu/Ax3amtOwk76u4h5J4eK2Nx2KB5qt0Z4cOapCsxot9VcEN11ADV5aUSlQF4RhGjQ==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/autocomplete-shared@1.17.9': + resolution: {integrity: sha512-iDf05JDQ7I0b7JEA/9IektxN/80a2MZ1ToohfmNS3rfeuQnIKI3IJlIafD0xu4StbtQTghx9T3Maa97ytkXenQ==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/client-abtesting@5.54.0': + resolution: {integrity: sha512-IXnH1x3DBsPQA4/FRO0Pvkfy79tKy5Qr+ugAV9jdcGkpzHc476D0WV1MFJ+pZxtbts0xh2JqzUVmYEqA6LkOpA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-analytics@5.54.0': + resolution: {integrity: sha512-pBpRqm1wpE0GnGy2rNLk9rjDn0Le4iywNRtnAWblLeqfjxpWKg8lWnk7nmSoTShFO31sz2jatXXzxK2lz8ipbA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-common@5.54.0': + resolution: {integrity: sha512-WbuwRUlFvSOsuxqTDjSSmgusuF5KFt+oFPzobvPDvodra6EWnVwUXjz0elkNSsnsIlZGtcXlX3LhxkO7rF90jw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-insights@5.54.0': + resolution: {integrity: sha512-/HNLVi3kPI+JhO59WbglLjPM2c4uECU+x4gk1iADseKtE5eYqJ/RJ+FIwM8xzPFKhFJaw+8hVq4lkd0nn8HDDg==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-personalization@5.54.0': + resolution: {integrity: sha512-6TolyyDRumIKeLGBGSFAZsSIJ8hrm6NFCGR1jO7pQTiOtSWgAIxcFE5/JRpZ3g+unG4OkNOFy+I0dUEquIDbig==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-query-suggestions@5.54.0': + resolution: {integrity: sha512-AxW2MLBhjBtGX5kIZrVLO2SP2vRkZxJw5qHGxnQJfLcYhA04mFNP0fWRtHshlcVnDk9M8L9lwfr2lGpf3Er+hw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-search@5.54.0': + resolution: {integrity: sha512-ngdgVGp05lJzUyA+sUzr0MDZ7AMtANcJpwIzq4ZsfpZL5B3S7A4XYfMcU2sECZc3bx3ysOhYcdbbaTjc3ve0WQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/ingestion@1.54.0': + resolution: {integrity: sha512-hee59Z7FgZ6/13FYL05ANPwRJY1pfvIlrwC8eBZYdiRFXTJVvf94IyTWOxLqRVpbseDP6eQQTW+PT7/DxTZIng==} + engines: {node: '>= 14.0.0'} + + '@algolia/monitoring@1.54.0': + resolution: {integrity: sha512-diCjZVbIO7Pzw98tEKqLWIAgmQBI3Zt1sHsXyAPNGZgn32derpIXTnjjpJbZl+uAhSSznd6SfxFGdC9uYdN1TA==} + engines: {node: '>= 14.0.0'} + + '@algolia/recommend@5.54.0': + resolution: {integrity: sha512-6zeslypRAGWDgVJEJYAPuqmyquHvnw4MQwG+XXdrw5dTNDjXYIcCJdQQcCY06xPF9tUvmzy/1vHiH9QxhgwuOQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-browser-xhr@5.54.0': + resolution: {integrity: sha512-iHZax214LPXd7XizQ4BNnTsegl8f3IeKm8JcrmSNZ/5x1rZ5xLkbG/anltAWtLpoRNnfpr4Z80YdYeVVPpx6wQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-fetch@5.54.0': + resolution: {integrity: sha512-YKtuG5YwPxZ+kkfd4HmUO7Z9aICPUSMlHslzKTmtMMhxGnetxEqGj9T/v2r/PdcuOUC5oW0CHe8akJk8cpS3gQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-node-http@5.54.0': + resolution: {integrity: sha512-zyZDJ4WS5TnjZZ5pqywTBFO9olW7QMtY2kf2dbLnu+UTzfc9ri/HGf27jRN2NTbX9FcRPxSqPqzUhF/BZIx0VA==} + engines: {node: '>= 14.0.0'} + + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + + '@astrojs/check@0.9.9': + resolution: {integrity: sha512-A5UW8uIuErLWEoRQvzgXpO1gTjUFtK8r7nU2Z7GewAMxUb7bPvpk11qaKKgxqXlHJWlAvaaxy+Xg28A6bmQ1Tg==} + hasBin: true + peerDependencies: + typescript: ^5.0.0 || ^6.0.0 + + '@astrojs/compiler@2.13.1': + resolution: {integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==} + + '@astrojs/internal-helpers@0.7.6': + resolution: {integrity: sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q==} + + '@astrojs/language-server@2.16.10': + resolution: {integrity: sha512-87VQ/5GSdHlRnUA+hGuerYyIGAj+9RbZmATyuKLEUePinUXhQ5YkRnRrHhOD9sSi5JOErLjrLkHnfZFEvGrV8w==} + hasBin: true + peerDependencies: + prettier: ^3.0.0 + prettier-plugin-astro: '>=0.11.0' + peerDependenciesMeta: + prettier: + optional: true + prettier-plugin-astro: + optional: true + + '@astrojs/markdown-remark@6.3.11': + resolution: {integrity: sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ==} + + '@astrojs/mdx@4.3.14': + resolution: {integrity: sha512-FBrqJQORVm+rkRa2TS5CjU9PBA6hkhrwLVBSS9A77gN2+iehvjq1w6yya/d0YKC7osiVorKkr3Qd9wNbl0ZkGA==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + peerDependencies: + astro: ^5.0.0 + + '@astrojs/prism@3.3.0': + resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/react@4.4.2': + resolution: {integrity: sha512-1tl95bpGfuaDMDn8O3x/5Dxii1HPvzjvpL2YTuqOOrQehs60I2DKiDgh1jrKc7G8lv+LQT5H15V6QONQ+9waeQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + peerDependencies: + '@types/react': ^17.0.50 || ^18.0.21 || ^19.0.0 + '@types/react-dom': ^17.0.17 || ^18.0.6 || ^19.0.0 + react: ^17.0.2 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0 + + '@astrojs/sitemap@3.7.2': + resolution: {integrity: sha512-PqkzkcZTb5ICiyIR8VoKbIAP/laNRXi5tw616N1Ckk+40oNB8Can1AzVV56lrbC5GKSZFCyJYUVYqVivMisvpA==} + + '@astrojs/telemetry@3.3.0': + resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/yaml2ts@0.2.4': + resolution: {integrity: sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A==} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + + '@capsizecss/unpack@4.0.0': + resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==} + engines: {node: '>=18'} + + '@chevrotain/cst-dts-gen@11.1.2': + resolution: {integrity: sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==} + + '@chevrotain/gast@11.1.2': + resolution: {integrity: sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==} + + '@chevrotain/regexp-to-ast@11.1.2': + resolution: {integrity: sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==} + + '@chevrotain/types@11.1.2': + resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} + + '@chevrotain/utils@11.1.2': + resolution: {integrity: sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==} + + '@ctrl/tinycolor@4.2.0': + resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} + engines: {node: '>=14'} + + '@docsearch/css@3.9.0': + resolution: {integrity: sha512-cQbnVbq0rrBwNAKegIac/t6a8nWoUAn8frnkLFW6YARaRmAQr5/Eoe6Ln2fqkUCZ40KpdrKbpSAmgrkviOxuWA==} + + '@docsearch/react@3.9.0': + resolution: {integrity: sha512-mb5FOZYZIkRQ6s/NWnM98k879vu5pscWqTLubLFBO87igYYT4VzVazh4h5o/zCvTIZgEt3PvsCOMOswOUo9yHQ==} + peerDependencies: + '@types/react': '>= 16.8.0 < 20.0.0' + react: '>= 16.8.0 < 20.0.0' + react-dom: '>= 16.8.0 < 20.0.0' + search-insights: '>= 1 < 3' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + + '@emmetio/abbreviation@2.3.3': + resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} + + '@emmetio/css-abbreviation@2.1.8': + resolution: {integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==} + + '@emmetio/css-parser@0.4.1': + resolution: {integrity: sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==} + + '@emmetio/html-matcher@1.3.0': + resolution: {integrity: sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==} + + '@emmetio/scanner@1.0.4': + resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} + + '@emmetio/stream-reader-utils@0.1.0': + resolution: {integrity: sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==} + + '@emmetio/stream-reader@2.2.0': + resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} + + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@expressive-code/core@0.40.2': + resolution: {integrity: sha512-gXY3v7jbgz6nWKvRpoDxK4AHUPkZRuJsM79vHX/5uhV9/qX6Qnctp/U/dMHog/LCVXcuOps+5nRmf1uxQVPb3w==} + + '@expressive-code/plugin-frames@0.40.2': + resolution: {integrity: sha512-aLw5IlDlZWb10Jo/TTDCVsmJhKfZ7FJI83Zo9VDrV0OBlmHAg7klZqw68VDz7FlftIBVAmMby53/MNXPnMjTSQ==} + + '@expressive-code/plugin-shiki@0.40.2': + resolution: {integrity: sha512-t2HMR5BO6GdDW1c1ISBTk66xO503e/Z8ecZdNcr6E4NpUfvY+MRje+LtrcvbBqMwWBBO8RpVKcam/Uy+1GxwKQ==} + + '@expressive-code/plugin-text-markers@0.40.2': + resolution: {integrity: sha512-/XoLjD67K9nfM4TgDlXAExzMJp6ewFKxNpfUw4F7q5Ecy+IU3/9zQQG/O70Zy+RxYTwKGw2MA9kd7yelsxnSmw==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.0': + resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + + '@mermaid-js/parser@1.1.0': + resolution: {integrity: sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==} + + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.60.1': + resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.1': + resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.1': + resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.1': + resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.1': + resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.1': + resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.60.1': + resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.60.1': + resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.60.1': + resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.60.1': + resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.60.1': + resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.1': + resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.1': + resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.1': + resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} + cpu: [x64] + os: [win32] + + '@shikijs/core@1.29.2': + resolution: {integrity: sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==} + + '@shikijs/core@3.23.0': + resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} + + '@shikijs/engine-javascript@1.29.2': + resolution: {integrity: sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==} + + '@shikijs/engine-javascript@3.23.0': + resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} + + '@shikijs/engine-oniguruma@1.29.2': + resolution: {integrity: sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==} + + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} + + '@shikijs/langs@1.29.2': + resolution: {integrity: sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==} + + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + + '@shikijs/themes@1.29.2': + resolution: {integrity: sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==} + + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + + '@shikijs/types@1.29.2': + resolution: {integrity: sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==} + + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/nlcst@2.0.3': + resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + + '@types/node@24.12.2': + resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.28': + resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + + '@types/sax@1.2.7': + resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + + '@vercel/functions@2.2.13': + resolution: {integrity: sha512-14ArBSIIcOBx9nrEgaJb4Bw+en1gl6eSoJWh8qjifLl5G3E4dRXCFOT8HP+w66vb9Wqyd1lAQBrmRhRwOj9X9A==} + engines: {node: '>= 18'} + peerDependencies: + '@aws-sdk/credential-provider-web-identity': '*' + peerDependenciesMeta: + '@aws-sdk/credential-provider-web-identity': + optional: true + + '@vercel/oidc@2.0.2': + resolution: {integrity: sha512-59PBFx3T+k5hLTEWa3ggiMpGRz1OVvl9eN8SUai+A43IsqiOuAe7qPBf+cray/Fj6mkgnxm/D7IAtjc8zSHi7g==} + engines: {node: '>= 18'} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@volar/kit@2.4.28': + resolution: {integrity: sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==} + peerDependencies: + typescript: '*' + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/language-server@2.4.28': + resolution: {integrity: sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==} + + '@volar/language-service@2.4.28': + resolution: {integrity: sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + + '@vscode/emmet-helper@2.11.0': + resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} + + '@vscode/l10n@0.0.18': + resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + algoliasearch@5.54.0: + resolution: {integrity: sha512-APAX4ajIOgsmYoUlGe++oNZkSTBgmXYM4maHC0OxC+Yo7xkaKQElV0ATZYCZA7jzrSJX1OBiqEs7mk+ZxXgYqA==} + engines: {node: '>= 14.0.0'} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-iterate@2.0.1: + resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + astro-expressive-code@0.40.2: + resolution: {integrity: sha512-yJMQId0yXSAbW9I6yqvJ3FcjKzJ8zRL7elbJbllkv1ZJPlsI0NI83Pxn1YL1IapEM347EvOOkSW2GL+2+NO61w==} + peerDependencies: + astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 + + astro@5.18.1: + resolution: {integrity: sha512-m4VWilWZ+Xt6NPoYzC4CgGZim/zQUO7WFL0RHCH0AiEavF1153iC3+me2atDvXpf/yX4PyGUeD8wZLq1cirT3g==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} + hasBin: true + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + base-64@1.0.0: + resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} + + baseline-browser-mapping@2.10.29: + resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + bcp-47-match@2.0.3: + resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + boxen@8.0.1: + resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} + engines: {node: '>=18'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + camelcase@8.0.0: + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} + engines: {node: '>=16'} + + caniuse-lite@1.0.30001792: + resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chevrotain-allstar@0.3.1: + resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} + peerDependencies: + chevrotain: ^11.0.0 + + chevrotain@11.1.2: + resolution: {integrity: sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + common-ancestor-path@1.0.1: + resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@1.2.3: + resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-selector-parser@3.3.0: + resolution: {integrity: sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==} + + css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.33.1: + resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} + + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + defu@6.1.6: + resolution: {integrity: sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug==} + + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + deterministic-object-hash@2.0.2: + resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} + engines: {node: '>=18'} + + devalue@5.6.4: + resolution: {integrity: sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + direction@2.0.1: + resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==} + hasBin: true + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + dompurify@3.3.3: + resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dset@3.1.4: + resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} + engines: {node: '>=4'} + + electron-to-chromium@1.5.355: + resolution: {integrity: sha512-LUPZhKzZPYSPme1jEYohpkA+ybYCJztr1quAdBd7E7h3+VOBVcKkwwtBJu41nrjawrRzfb8mtMfzWozoaK0ZIQ==} + + emmet@2.4.11: + resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} + + emoji-regex-xs@1.0.0: + resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expressive-code@0.40.2: + resolution: {integrity: sha512-1zIda2rB0qiDZACawzw2rbdBQiWHBT56uBctS+ezFe5XMAaFaHLnnSYND/Kd+dVzO9HfCXRDpzH3d+3fvOWRcw==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + flattie@1.1.1: + resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} + engines: {node: '>=8'} + + fontace@0.4.1: + resolution: {integrity: sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==} + + fontkitten@1.0.3: + resolution: {integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==} + engines: {node: '>=20'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + + h3@1.15.11: + resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} + + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-has-property@3.0.0: + resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-select@6.0.4: + resolution: {integrity: sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + iron-webcrypto@1.2.1: + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@2.3.1: + resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + katex@0.16.44: + resolution: {integrity: sha512-EkxoDTk8ufHqHlf9QxGwcxeLkWRR3iOuYfRpfORgYfqc8s13bgb+YtRY59NK5ZpRaCwq1kqA6a5lpX8C/eLphQ==} + hasBin: true + + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + langium@4.2.1: + resolution: {integrity: sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==} + engines: {node: '>=20.10.0', npm: '>=10.2.3'} + + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + + lodash-es@4.17.23: + resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@11.2.7: + resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + + mdast-util-definitions@6.0.0: + resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + mermaid@11.14.0: + resolution: {integrity: sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + neotraverse@0.6.18: + resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} + engines: {node: '>= 10'} + + nlcst-to-string@4.0.0: + resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-mock-http@1.0.4: + resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} + + node-releases@2.0.44: + resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@2.3.0: + resolution: {integrity: sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==} + + oniguruma-to-es@4.3.5: + resolution: {integrity: sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==} + + p-limit@6.2.0: + resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} + engines: {node: '>=18'} + + p-queue@8.1.1: + resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==} + engines: {node: '>=18'} + + p-timeout@6.1.4: + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} + + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse-latin@7.0.0: + resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + piccolore@0.1.3: + resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-icons@5.6.0: + resolution: {integrity: sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==} + peerDependencies: + react: '*' + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + regex-recursion@5.1.1: + resolution: {integrity: sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@5.1.1: + resolution: {integrity: sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + rehype-expressive-code@0.40.2: + resolution: {integrity: sha512-+kn+AMGCrGzvtH8Q5lC6Y5lnmTV/r33fdmi5QU/IH1KPHKobKr5UnLwJuqHv5jBTSN/0v2wLDS7RTM73FVzqmQ==} + + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + + rehype@13.0.2: + resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-smartypants@3.0.2: + resolution: {integrity: sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==} + engines: {node: '>=16.0.0'} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + request-light@0.5.8: + resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==} + + request-light@0.7.0: + resolution: {integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + retext-latin@4.0.0: + resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} + + retext-smartypants@6.2.0: + resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} + + retext-stringify@4.0.0: + resolution: {integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==} + + retext@9.0.0: + resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==} + + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + + rollup@4.60.1: + resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + search-insights@2.17.3: + resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.4: + resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shiki@1.29.2: + resolution: {integrity: sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==} + + shiki@3.23.0: + resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + sitemap@9.0.1: + resolution: {integrity: sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==} + engines: {node: '>=20.19.5', npm: '>=10.8.2'} + hasBin: true + + smol-toml@1.6.1: + resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} + engines: {node: '>= 18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + stream-replace-string@2.0.0: + resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + + svgo@4.0.1: + resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==} + engines: {node: '>=16'} + hasBin: true + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + tinyexec@1.0.4: + resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typesafe-path@0.2.2: + resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} + + typescript-auto-import-cache@0.3.6: + resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + ultrahtml@1.6.0: + resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} + + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unifont@0.7.4: + resolution: {integrity: sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-modify-children@4.0.0: + resolution: {integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-children@3.0.0: + resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + unstorage@1.17.5: + resolution: {integrity: sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6 || ^7 || ^8 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1 || ^2 || ^3 + aws4fetch: ^1.0.20 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@6.4.2: + resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + + volar-service-css@0.0.70: + resolution: {integrity: sha512-K1qyOvBpE3rzdAv3e4/6Rv5yizrYPy5R/ne3IWCAzLBuMO4qBMV3kSqWzj6KUVe6S0AnN6wxF7cRkiaKfYMYJw==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-emmet@0.0.70: + resolution: {integrity: sha512-xi5bC4m/VyE3zy/n2CXspKeDZs3qA41tHLTw275/7dNWM/RqE2z3BnDICQybHIVp/6G1iOQj5c1qXMgQC08TNg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-html@0.0.70: + resolution: {integrity: sha512-eR6vCgMdmYAo4n+gcT7DSyBQbwB8S3HZZvSagTf0sxNaD4WppMCFfpqWnkrlGStPKMZvMiejRRVmqsX9dYcTvQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-prettier@0.0.70: + resolution: {integrity: sha512-Z6BCFSpGVCd8BPAsZ785Kce1BGlWd5ODqmqZGVuB14MJvrR4+CYz6cDy4F+igmE1gMifqfvMhdgT8Aud4M5ngg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + prettier: ^2.2 || ^3.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + prettier: + optional: true + + volar-service-typescript-twoslash-queries@0.0.70: + resolution: {integrity: sha512-IdD13Z9N2Bu8EM6CM0fDV1E69olEYGHDU25X51YXmq8Y0CmJ2LNj6gOiBJgpS5JGUqFzECVhMNBW7R0sPdRTMQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-typescript@0.0.70: + resolution: {integrity: sha512-l46Bx4cokkUedTd74ojO5H/zqHZJ8SUuyZ0IB8JN4jfRqUM3bQFBHoOwlZCyZmOeO0A3RQNkMnFclxO4c++gsg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-yaml@0.0.70: + resolution: {integrity: sha512-0c8bXDBeoATF9F6iPIlOuYTuZAC4c+yi0siQo920u7eiBJk8oQmUmg9cDUbR4+Gl++bvGP4plj3fErbJuPqdcQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + vscode-css-languageservice@6.3.10: + resolution: {integrity: sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==} + + vscode-html-languageservice@5.6.2: + resolution: {integrity: sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg==} + + vscode-json-languageservice@4.1.8: + resolution: {integrity: sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==} + engines: {npm: '>=7.0.0'} + + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-nls@5.2.0: + resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + which-pm-runs@1.1.0: + resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} + engines: {node: '>=4'} + + widest-line@5.0.0: + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} + engines: {node: '>=18'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + xxhash-wasm@1.1.0: + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml-language-server@1.20.0: + resolution: {integrity: sha512-qhjK/bzSRZ6HtTvgeFvjNPJGWdZ0+x5NREV/9XZWFjIGezew2b4r5JPy66IfOhd5OA7KeFwk1JfmEbnTvev0cA==} + hasBin: true + + yaml@2.7.1: + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} + engines: {node: '>= 14'} + hasBin: true + + yaml@2.8.3: + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + yocto-spinner@0.2.3: + resolution: {integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==} + engines: {node: '>=18.19'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod-to-ts@1.2.0: + resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==} + peerDependencies: + typescript: ^4.9.4 || ^5.0.2 + zod: ^3 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@algolia/abtesting@1.20.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/autocomplete-core@1.17.9(@algolia/client-search@5.54.0)(algoliasearch@5.54.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.17.9(@algolia/client-search@5.54.0)(algoliasearch@5.54.0)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.9(@algolia/client-search@5.54.0)(algoliasearch@5.54.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + + '@algolia/autocomplete-plugin-algolia-insights@1.17.9(@algolia/client-search@5.54.0)(algoliasearch@5.54.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-shared': 1.17.9(@algolia/client-search@5.54.0)(algoliasearch@5.54.0) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + + '@algolia/autocomplete-preset-algolia@1.17.9(@algolia/client-search@5.54.0)(algoliasearch@5.54.0)': + dependencies: + '@algolia/autocomplete-shared': 1.17.9(@algolia/client-search@5.54.0)(algoliasearch@5.54.0) + '@algolia/client-search': 5.54.0 + algoliasearch: 5.54.0 + + '@algolia/autocomplete-shared@1.17.9(@algolia/client-search@5.54.0)(algoliasearch@5.54.0)': + dependencies: + '@algolia/client-search': 5.54.0 + algoliasearch: 5.54.0 + + '@algolia/client-abtesting@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/client-analytics@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/client-common@5.54.0': {} + + '@algolia/client-insights@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/client-personalization@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/client-query-suggestions@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/client-search@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/ingestion@1.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/monitoring@1.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/recommend@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + '@algolia/requester-browser-xhr@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + + '@algolia/requester-fetch@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + + '@algolia/requester-node-http@5.54.0': + dependencies: + '@algolia/client-common': 5.54.0 + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.0.4 + + '@astrojs/check@0.9.9(prettier@3.8.3)(typescript@5.9.3)': + dependencies: + '@astrojs/language-server': 2.16.10(prettier@3.8.3)(typescript@5.9.3) + chokidar: 4.0.3 + kleur: 4.1.5 + typescript: 5.9.3 + yargs: 17.7.2 + transitivePeerDependencies: + - prettier + - prettier-plugin-astro + + '@astrojs/compiler@2.13.1': {} + + '@astrojs/internal-helpers@0.7.6': {} + + '@astrojs/language-server@2.16.10(prettier@3.8.3)(typescript@5.9.3)': + dependencies: + '@astrojs/compiler': 2.13.1 + '@astrojs/yaml2ts': 0.2.4 + '@jridgewell/sourcemap-codec': 1.5.5 + '@volar/kit': 2.4.28(typescript@5.9.3) + '@volar/language-core': 2.4.28 + '@volar/language-server': 2.4.28 + '@volar/language-service': 2.4.28 + muggle-string: 0.4.1 + tinyglobby: 0.2.17 + volar-service-css: 0.0.70(@volar/language-service@2.4.28) + volar-service-emmet: 0.0.70(@volar/language-service@2.4.28) + volar-service-html: 0.0.70(@volar/language-service@2.4.28) + volar-service-prettier: 0.0.70(@volar/language-service@2.4.28)(prettier@3.8.3) + volar-service-typescript: 0.0.70(@volar/language-service@2.4.28) + volar-service-typescript-twoslash-queries: 0.0.70(@volar/language-service@2.4.28) + volar-service-yaml: 0.0.70(@volar/language-service@2.4.28) + vscode-html-languageservice: 5.6.2 + vscode-uri: 3.1.0 + optionalDependencies: + prettier: 3.8.3 + transitivePeerDependencies: + - typescript + + '@astrojs/markdown-remark@6.3.11': + dependencies: + '@astrojs/internal-helpers': 0.7.6 + '@astrojs/prism': 3.3.0 + github-slugger: 2.0.0 + hast-util-from-html: 2.0.3 + hast-util-to-text: 4.0.2 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + mdast-util-definitions: 6.0.0 + rehype-raw: 7.0.0 + rehype-stringify: 10.0.1 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + remark-smartypants: 3.0.2 + shiki: 3.23.0 + smol-toml: 1.6.1 + unified: 11.0.5 + unist-util-remove-position: 5.0.0 + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/mdx@4.3.14(astro@5.18.1(@types/node@24.12.2)(@vercel/functions@2.2.13)(rollup@4.60.1)(typescript@5.9.3)(yaml@2.8.3))': + dependencies: + '@astrojs/markdown-remark': 6.3.11 + '@mdx-js/mdx': 3.1.1 + acorn: 8.16.0 + astro: 5.18.1(@types/node@24.12.2)(@vercel/functions@2.2.13)(rollup@4.60.1)(typescript@5.9.3)(yaml@2.8.3) + es-module-lexer: 1.7.0 + estree-util-visit: 2.0.0 + hast-util-to-html: 9.0.5 + piccolore: 0.1.3 + rehype-raw: 7.0.0 + remark-gfm: 4.0.1 + remark-smartypants: 3.0.2 + source-map: 0.7.6 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/prism@3.3.0': + dependencies: + prismjs: 1.30.0 + + '@astrojs/react@4.4.2(@types/node@24.12.2)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yaml@2.8.3)': + dependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@vitejs/plugin-react': 4.7.0(vite@6.4.2(@types/node@24.12.2)(yaml@2.8.3)) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + ultrahtml: 1.6.0 + vite: 6.4.2(@types/node@24.12.2)(yaml@2.8.3) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + '@astrojs/sitemap@3.7.2': + dependencies: + sitemap: 9.0.1 + stream-replace-string: 2.0.0 + zod: 4.3.6 + + '@astrojs/telemetry@3.3.0': + dependencies: + ci-info: 4.4.0 + debug: 4.4.3 + dlv: 1.1.3 + dset: 3.1.4 + is-docker: 3.0.0 + is-wsl: 3.1.1 + which-pm-runs: 1.1.0 + transitivePeerDependencies: + - supports-color + + '@astrojs/yaml2ts@0.2.4': + dependencies: + yaml: 2.8.3 + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.0) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.2 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@braintree/sanitize-url@7.1.2': {} + + '@capsizecss/unpack@4.0.0': + dependencies: + fontkitten: 1.0.3 + + '@chevrotain/cst-dts-gen@11.1.2': + dependencies: + '@chevrotain/gast': 11.1.2 + '@chevrotain/types': 11.1.2 + lodash-es: 4.17.23 + + '@chevrotain/gast@11.1.2': + dependencies: + '@chevrotain/types': 11.1.2 + lodash-es: 4.17.23 + + '@chevrotain/regexp-to-ast@11.1.2': {} + + '@chevrotain/types@11.1.2': {} + + '@chevrotain/utils@11.1.2': {} + + '@ctrl/tinycolor@4.2.0': {} + + '@docsearch/css@3.9.0': {} + + '@docsearch/react@3.9.0(@algolia/client-search@5.54.0)(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-core': 1.17.9(@algolia/client-search@5.54.0)(algoliasearch@5.54.0)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.9(@algolia/client-search@5.54.0)(algoliasearch@5.54.0) + '@docsearch/css': 3.9.0 + algoliasearch: 5.54.0 + optionalDependencies: + '@types/react': 18.3.28 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + + '@emmetio/abbreviation@2.3.3': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-abbreviation@2.1.8': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-parser@0.4.1': + dependencies: + '@emmetio/stream-reader': 2.2.0 + '@emmetio/stream-reader-utils': 0.1.0 + + '@emmetio/html-matcher@1.3.0': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/scanner@1.0.4': {} + + '@emmetio/stream-reader-utils@0.1.0': {} + + '@emmetio/stream-reader@2.2.0': {} + + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@expressive-code/core@0.40.2': + dependencies: + '@ctrl/tinycolor': 4.2.0 + hast-util-select: 6.0.4 + hast-util-to-html: 9.0.5 + hast-util-to-text: 4.0.2 + hastscript: 9.0.1 + postcss: 8.5.8 + postcss-nested: 6.2.0(postcss@8.5.8) + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 + + '@expressive-code/plugin-frames@0.40.2': + dependencies: + '@expressive-code/core': 0.40.2 + + '@expressive-code/plugin-shiki@0.40.2': + dependencies: + '@expressive-code/core': 0.40.2 + shiki: 1.29.2 + + '@expressive-code/plugin-text-markers@0.40.2': + dependencies: + '@expressive-code/core': 0.40.2 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.0': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + mlly: 1.8.2 + + '@img/colour@1.1.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.9.2 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@mdx-js/mdx@3.1.1': + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 + acorn: 8.16.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.16.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@mermaid-js/parser@1.1.0': + dependencies: + langium: 4.2.1 + + '@oslojs/encoding@1.1.0': {} + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/pluginutils@5.3.0(rollup@4.60.1)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.4 + optionalDependencies: + rollup: 4.60.1 + + '@rollup/rollup-android-arm-eabi@4.60.1': + optional: true + + '@rollup/rollup-android-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-x64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.1': + optional: true + + '@shikijs/core@1.29.2': + dependencies: + '@shikijs/engine-javascript': 1.29.2 + '@shikijs/engine-oniguruma': 1.29.2 + '@shikijs/types': 1.29.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/core@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@1.29.2': + dependencies: + '@shikijs/types': 1.29.2 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 2.3.0 + + '@shikijs/engine-javascript@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.5 + + '@shikijs/engine-oniguruma@1.29.2': + dependencies: + '@shikijs/types': 1.29.2 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/engine-oniguruma@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@1.29.2': + dependencies: + '@shikijs/types': 1.29.2 + + '@shikijs/langs@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/themes@1.29.2': + dependencies: + '@shikijs/types': 1.29.2 + + '@shikijs/themes@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/types@1.29.2': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/types@3.23.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + + '@types/estree@1.0.8': {} + + '@types/geojson@7946.0.16': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.13': {} + + '@types/ms@2.1.0': {} + + '@types/nlcst@2.0.3': + dependencies: + '@types/unist': 3.0.3 + + '@types/node@24.12.2': + dependencies: + undici-types: 7.16.0 + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.28)': + dependencies: + '@types/react': 18.3.28 + + '@types/react@18.3.28': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/sax@1.2.7': + dependencies: + '@types/node': 24.12.2 + + '@types/trusted-types@2.0.7': + optional: true + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.0': {} + + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + '@vercel/functions@2.2.13': + dependencies: + '@vercel/oidc': 2.0.2 + optional: true + + '@vercel/oidc@2.0.2': + dependencies: + '@types/ms': 2.1.0 + ms: 2.1.3 + optional: true + + '@vitejs/plugin-react@4.7.0(vite@6.4.2(@types/node@24.12.2)(yaml@2.8.3))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.2(@types/node@24.12.2)(yaml@2.8.3) + transitivePeerDependencies: + - supports-color + + '@volar/kit@2.4.28(typescript@5.9.3)': + dependencies: + '@volar/language-service': 2.4.28 + '@volar/typescript': 2.4.28 + typesafe-path: 0.2.2 + typescript: 5.9.3 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/language-server@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + '@volar/language-service': 2.4.28 + '@volar/typescript': 2.4.28 + path-browserify: 1.0.1 + request-light: 0.7.0 + vscode-languageserver: 9.0.1 + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/language-service@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vscode/emmet-helper@2.11.0': + dependencies: + emmet: 2.4.11 + jsonc-parser: 2.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + '@vscode/l10n@0.0.18': {} + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv-draft-04@1.0.0(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + algoliasearch@5.54.0: + dependencies: + '@algolia/abtesting': 1.20.0 + '@algolia/client-abtesting': 5.54.0 + '@algolia/client-analytics': 5.54.0 + '@algolia/client-common': 5.54.0 + '@algolia/client-insights': 5.54.0 + '@algolia/client-personalization': 5.54.0 + '@algolia/client-query-suggestions': 5.54.0 + '@algolia/client-search': 5.54.0 + '@algolia/ingestion': 1.54.0 + '@algolia/monitoring': 1.54.0 + '@algolia/recommend': 5.54.0 + '@algolia/requester-browser-xhr': 5.54.0 + '@algolia/requester-fetch': 5.54.0 + '@algolia/requester-node-http': 5.54.0 + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-iterate@2.0.1: {} + + astring@1.9.0: {} + + astro-expressive-code@0.40.2(astro@5.18.1(@types/node@24.12.2)(@vercel/functions@2.2.13)(rollup@4.60.1)(typescript@5.9.3)(yaml@2.8.3)): + dependencies: + astro: 5.18.1(@types/node@24.12.2)(@vercel/functions@2.2.13)(rollup@4.60.1)(typescript@5.9.3)(yaml@2.8.3) + rehype-expressive-code: 0.40.2 + + astro@5.18.1(@types/node@24.12.2)(@vercel/functions@2.2.13)(rollup@4.60.1)(typescript@5.9.3)(yaml@2.8.3): + dependencies: + '@astrojs/compiler': 2.13.1 + '@astrojs/internal-helpers': 0.7.6 + '@astrojs/markdown-remark': 6.3.11 + '@astrojs/telemetry': 3.3.0 + '@capsizecss/unpack': 4.0.0 + '@oslojs/encoding': 1.1.0 + '@rollup/pluginutils': 5.3.0(rollup@4.60.1) + acorn: 8.16.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + boxen: 8.0.1 + ci-info: 4.4.0 + clsx: 2.1.1 + common-ancestor-path: 1.0.1 + cookie: 1.1.1 + cssesc: 3.0.0 + debug: 4.4.3 + deterministic-object-hash: 2.0.2 + devalue: 5.6.4 + diff: 8.0.4 + dlv: 1.1.3 + dset: 3.1.4 + es-module-lexer: 1.7.0 + esbuild: 0.27.7 + estree-walker: 3.0.3 + flattie: 1.1.1 + fontace: 0.4.1 + github-slugger: 2.0.0 + html-escaper: 3.0.3 + http-cache-semantics: 4.2.0 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + magic-string: 0.30.21 + magicast: 0.5.2 + mrmime: 2.0.1 + neotraverse: 0.6.18 + p-limit: 6.2.0 + p-queue: 8.1.1 + package-manager-detector: 1.6.0 + piccolore: 0.1.3 + picomatch: 4.0.4 + prompts: 2.4.2 + rehype: 13.0.2 + semver: 7.8.4 + shiki: 3.23.0 + smol-toml: 1.6.1 + svgo: 4.0.1 + tinyexec: 1.0.4 + tinyglobby: 0.2.17 + tsconfck: 3.1.6(typescript@5.9.3) + ultrahtml: 1.6.0 + unifont: 0.7.4 + unist-util-visit: 5.1.0 + unstorage: 1.17.5(@vercel/functions@2.2.13) + vfile: 6.0.3 + vite: 6.4.2(@types/node@24.12.2)(yaml@2.8.3) + vitefu: 1.1.3(vite@6.4.2(@types/node@24.12.2)(yaml@2.8.3)) + xxhash-wasm: 1.1.0 + yargs-parser: 21.1.1 + yocto-spinner: 0.2.3 + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) + zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76) + optionalDependencies: + sharp: 0.34.5 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - db0 + - idb-keyval + - ioredis + - jiti + - less + - lightningcss + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - uploadthing + - yaml + + axobject-query@4.1.0: {} + + bail@2.0.2: {} + + base-64@1.0.0: {} + + baseline-browser-mapping@2.10.29: {} + + bcp-47-match@2.0.3: {} + + boolbase@1.0.0: {} + + boxen@8.0.1: + dependencies: + ansi-align: 3.0.1 + camelcase: 8.0.0 + chalk: 5.6.2 + cli-boxes: 3.0.0 + string-width: 7.2.0 + type-fest: 4.41.0 + widest-line: 5.0.0 + wrap-ansi: 9.0.2 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.29 + caniuse-lite: 1.0.30001792 + electron-to-chromium: 1.5.355 + node-releases: 2.0.44 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + camelcase@8.0.0: {} + + caniuse-lite@1.0.30001792: {} + + ccount@2.0.1: {} + + chalk@5.6.2: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chevrotain-allstar@0.3.1(chevrotain@11.1.2): + dependencies: + chevrotain: 11.1.2 + lodash-es: 4.18.1 + + chevrotain@11.1.2: + dependencies: + '@chevrotain/cst-dts-gen': 11.1.2 + '@chevrotain/gast': 11.1.2 + '@chevrotain/regexp-to-ast': 11.1.2 + '@chevrotain/types': 11.1.2 + '@chevrotain/utils': 11.1.2 + lodash-es: 4.17.23 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + ci-info@4.4.0: {} + + cli-boxes@3.0.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@2.1.1: {} + + collapse-white-space@2.1.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + comma-separated-tokens@2.0.3: {} + + commander@11.1.0: {} + + commander@7.2.0: {} + + commander@8.3.0: {} + + common-ancestor-path@1.0.1: {} + + confbox@0.1.8: {} + + convert-source-map@2.0.0: {} + + cookie-es@1.2.3: {} + + cookie@1.1.1: {} + + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-selector-parser@3.3.0: {} + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + + cssesc@3.0.0: {} + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + csstype@3.2.3: {} + + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.1 + + cytoscape-fcose@2.2.0(cytoscape@3.33.1): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.33.1 + + cytoscape@3.33.1: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.14: + dependencies: + d3: 7.9.0 + lodash-es: 4.18.1 + + dayjs@1.11.20: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + defu@6.1.6: {} + + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + + dequal@2.0.3: {} + + destr@2.0.5: {} + + detect-libc@2.1.2: + optional: true + + deterministic-object-hash@2.0.2: + dependencies: + base-64: 1.0.0 + + devalue@5.6.4: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff@8.0.4: {} + + direction@2.0.1: {} + + dlv@1.1.3: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + dompurify@3.3.3: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dset@3.1.4: {} + + electron-to-chromium@1.5.355: {} + + emmet@2.4.11: + dependencies: + '@emmetio/abbreviation': 2.3.3 + '@emmetio/css-abbreviation': 2.1.8 + + emoji-regex-xs@1.0.0: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + entities@4.5.0: {} + + entities@6.0.1: {} + + es-module-lexer@1.7.0: {} + + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.16.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escalade@3.2.0: {} + + escape-string-regexp@5.0.0: {} + + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.8 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + eventemitter3@5.0.4: {} + + expressive-code@0.40.2: + dependencies: + '@expressive-code/core': 0.40.2 + '@expressive-code/plugin-frames': 0.40.2 + '@expressive-code/plugin-shiki': 0.40.2 + '@expressive-code/plugin-text-markers': 0.40.2 + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.0: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + flattie@1.1.1: {} + + fontace@0.4.1: + dependencies: + fontkitten: 1.0.3 + + fontkitten@1.0.3: + dependencies: + tiny-inflate: 1.0.3 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.5.0: {} + + github-slugger@2.0.0: {} + + h3@1.15.11: + dependencies: + cookie-es: 1.2.3 + crossws: 0.3.5 + defu: 6.1.6 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.4 + radix3: 1.1.2 + ufo: 1.6.3 + uncrypto: 0.1.3 + + hachure-fill@0.5.2: {} + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-has-property@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-select@6.0.4: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + bcp-47-match: 2.0.3 + comma-separated-tokens: 2.0.3 + css-selector-parser: 3.3.0 + devlop: 1.1.0 + direction: 2.0.1 + hast-util-has-property: 3.0.0 + hast-util-to-string: 3.0.1 + hast-util-whitespace: 3.0.0 + nth-check: 2.1.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-string@3.0.1: + dependencies: + '@types/hast': 3.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + + html-escaper@3.0.3: {} + + html-void-elements@3.0.0: {} + + http-cache-semantics@4.2.0: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + import-meta-resolve@4.2.0: {} + + inline-style-parser@0.2.7: {} + + internmap@1.0.1: {} + + internmap@2.0.3: {} + + iron-webcrypto@1.2.1: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-decimal@2.0.1: {} + + is-docker@3.0.0: {} + + is-fullwidth-code-point@3.0.0: {} + + is-hexadecimal@2.0.1: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-plain-obj@4.1.0: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-schema-traverse@1.0.0: {} + + json5@2.2.3: {} + + jsonc-parser@2.3.1: {} + + jsonc-parser@3.3.1: {} + + katex@0.16.44: + dependencies: + commander: 8.3.0 + + khroma@2.1.0: {} + + kleur@3.0.3: {} + + kleur@4.1.5: {} + + langium@4.2.1: + dependencies: + chevrotain: 11.1.2 + chevrotain-allstar: 0.3.1(chevrotain@11.1.2) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + + lodash-es@4.17.23: {} + + lodash-es@4.18.1: {} + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@11.2.7: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.2: + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + markdown-extensions@2.0.0: {} + + markdown-table@3.0.4: {} + + marked@16.4.2: {} + + mdast-util-definitions@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdn-data@2.0.28: {} + + mdn-data@2.27.1: {} + + mermaid@11.14.0: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.0 + '@mermaid-js/parser': 1.1.0 + '@types/d3': 7.4.3 + '@upsetjs/venn.js': 2.0.0 + cytoscape: 3.33.1 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) + cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.14 + dayjs: 1.11.20 + dompurify: 3.3.3 + katex: 0.16.44 + khroma: 2.1.0 + lodash-es: 4.18.1 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 11.1.0 + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.8 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + mlly@1.8.2: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + neotraverse@0.6.18: {} + + nlcst-to-string@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + + node-fetch-native@1.6.7: {} + + node-mock-http@1.0.4: {} + + node-releases@2.0.44: {} + + normalize-path@3.0.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.3 + + ohash@2.0.11: {} + + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@2.3.0: + dependencies: + emoji-regex-xs: 1.0.0 + regex: 5.1.1 + regex-recursion: 5.1.1 + + oniguruma-to-es@4.3.5: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.1.0 + regex-recursion: 6.0.2 + + p-limit@6.2.0: + dependencies: + yocto-queue: 1.2.2 + + p-queue@8.1.1: + dependencies: + eventemitter3: 5.0.4 + p-timeout: 6.1.4 + + p-timeout@6.1.4: {} + + package-manager-detector@1.6.0: {} + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-latin@7.0.0: + dependencies: + '@types/nlcst': 2.0.3 + '@types/unist': 3.0.3 + nlcst-to-string: 4.0.0 + unist-util-modify-children: 4.0.0 + unist-util-visit-children: 3.0.0 + vfile: 6.0.3 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-browserify@1.0.1: {} + + path-data-parser@0.1.0: {} + + pathe@2.0.3: {} + + piccolore@0.1.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + + postcss-nested@6.2.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.8.3: {} + + prismjs@1.30.0: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + property-information@7.1.0: {} + + radix3@1.1.2: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-icons@5.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + + react-refresh@0.17.0: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + readdirp@4.1.2: {} + + readdirp@5.0.0: {} + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.8 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + regex-recursion@5.1.1: + dependencies: + regex: 5.1.1 + regex-utilities: 2.3.0 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@5.1.1: + dependencies: + regex-utilities: 2.3.0 + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rehype-expressive-code@0.40.2: + dependencies: + expressive-code: 0.40.2 + + rehype-parse@9.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + + rehype@13.0.2: + dependencies: + '@types/hast': 3.0.4 + rehype-parse: 9.0.1 + rehype-stringify: 10.0.1 + unified: 11.0.5 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-smartypants@3.0.2: + dependencies: + retext: 9.0.0 + retext-smartypants: 6.2.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + request-light@0.5.8: {} + + request-light@0.7.0: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + retext-latin@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + parse-latin: 7.0.0 + unified: 11.0.5 + + retext-smartypants@6.2.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unist-util-visit: 5.1.0 + + retext-stringify@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unified: 11.0.5 + + retext@9.0.0: + dependencies: + '@types/nlcst': 2.0.3 + retext-latin: 4.0.0 + retext-stringify: 4.0.0 + unified: 11.0.5 + + robust-predicates@3.0.3: {} + + rollup@4.60.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.1 + '@rollup/rollup-android-arm64': 4.60.1 + '@rollup/rollup-darwin-arm64': 4.60.1 + '@rollup/rollup-darwin-x64': 4.60.1 + '@rollup/rollup-freebsd-arm64': 4.60.1 + '@rollup/rollup-freebsd-x64': 4.60.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 + '@rollup/rollup-linux-arm-musleabihf': 4.60.1 + '@rollup/rollup-linux-arm64-gnu': 4.60.1 + '@rollup/rollup-linux-arm64-musl': 4.60.1 + '@rollup/rollup-linux-loong64-gnu': 4.60.1 + '@rollup/rollup-linux-loong64-musl': 4.60.1 + '@rollup/rollup-linux-ppc64-gnu': 4.60.1 + '@rollup/rollup-linux-ppc64-musl': 4.60.1 + '@rollup/rollup-linux-riscv64-gnu': 4.60.1 + '@rollup/rollup-linux-riscv64-musl': 4.60.1 + '@rollup/rollup-linux-s390x-gnu': 4.60.1 + '@rollup/rollup-linux-x64-gnu': 4.60.1 + '@rollup/rollup-linux-x64-musl': 4.60.1 + '@rollup/rollup-openbsd-x64': 4.60.1 + '@rollup/rollup-openharmony-arm64': 4.60.1 + '@rollup/rollup-win32-arm64-msvc': 4.60.1 + '@rollup/rollup-win32-ia32-msvc': 4.60.1 + '@rollup/rollup-win32-x64-gnu': 4.60.1 + '@rollup/rollup-win32-x64-msvc': 4.60.1 + fsevents: 2.3.3 + + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + + rw@1.3.3: {} + + safer-buffer@2.1.2: {} + + sax@1.6.0: {} + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + search-insights@2.17.3: {} + + semver@6.3.1: {} + + semver@7.8.4: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + shiki@1.29.2: + dependencies: + '@shikijs/core': 1.29.2 + '@shikijs/engine-javascript': 1.29.2 + '@shikijs/engine-oniguruma': 1.29.2 + '@shikijs/langs': 1.29.2 + '@shikijs/themes': 1.29.2 + '@shikijs/types': 1.29.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + shiki@3.23.0: + dependencies: + '@shikijs/core': 3.23.0 + '@shikijs/engine-javascript': 3.23.0 + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + sisteransi@1.0.5: {} + + sitemap@9.0.1: + dependencies: + '@types/node': 24.12.2 + '@types/sax': 1.2.7 + arg: 5.0.2 + sax: 1.6.0 + + smol-toml@1.6.1: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + + stream-replace-string@2.0.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + stylis@4.3.6: {} + + svgo@4.0.1: + dependencies: + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.2.1 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.6.0 + + tiny-inflate@1.0.3: {} + + tinyexec@1.0.4: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + ts-dedent@2.2.0: {} + + tsconfck@3.1.6(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + tslib@2.8.1: + optional: true + + type-fest@4.41.0: {} + + typesafe-path@0.2.2: {} + + typescript-auto-import-cache@0.3.6: + dependencies: + semver: 7.8.4 + + typescript@5.9.3: {} + + ufo@1.6.3: {} + + ultrahtml@1.6.0: {} + + uncrypto@0.1.3: {} + + undici-types@7.16.0: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unifont@0.7.4: + dependencies: + css-tree: 3.2.1 + ofetch: 1.5.1 + ohash: 2.0.11 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-modify-children@4.0.0: + dependencies: + '@types/unist': 3.0.3 + array-iterate: 2.0.1 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-children@3.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + unstorage@1.17.5(@vercel/functions@2.2.13): + dependencies: + anymatch: 3.1.3 + chokidar: 5.0.0 + destr: 2.0.5 + h3: 1.15.11 + lru-cache: 11.2.7 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.3 + optionalDependencies: + '@vercel/functions': 2.2.13 + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + util-deprecate@1.0.2: {} + + uuid@11.1.0: {} + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@6.4.2(@types/node@24.12.2)(yaml@2.8.3): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.8 + rollup: 4.60.1 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.12.2 + fsevents: 2.3.3 + yaml: 2.8.3 + + vitefu@1.1.3(vite@6.4.2(@types/node@24.12.2)(yaml@2.8.3)): + optionalDependencies: + vite: 6.4.2(@types/node@24.12.2)(yaml@2.8.3) + + volar-service-css@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-css-languageservice: 6.3.10 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-emmet@0.0.70(@volar/language-service@2.4.28): + dependencies: + '@emmetio/css-parser': 0.4.1 + '@emmetio/html-matcher': 1.3.0 + '@vscode/emmet-helper': 2.11.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-html@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-html-languageservice: 5.6.2 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-prettier@0.0.70(@volar/language-service@2.4.28)(prettier@3.8.3): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + prettier: 3.8.3 + + volar-service-typescript-twoslash-queries@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-typescript@0.0.70(@volar/language-service@2.4.28): + dependencies: + path-browserify: 1.0.1 + semver: 7.8.4 + typescript-auto-import-cache: 0.3.6 + vscode-languageserver-textdocument: 1.0.12 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + volar-service-yaml@0.0.70(@volar/language-service@2.4.28): + dependencies: + vscode-uri: 3.1.0 + yaml-language-server: 1.20.0 + optionalDependencies: + '@volar/language-service': 2.4.28 + + vscode-css-languageservice@6.3.10: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-html-languageservice@5.6.2: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-json-languageservice@4.1.8: + dependencies: + jsonc-parser: 3.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-nls@5.2.0: {} + + vscode-uri@3.1.0: {} + + web-namespaces@2.0.1: {} + + which-pm-runs@1.1.0: {} + + widest-line@5.0.0: + dependencies: + string-width: 7.2.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + xxhash-wasm@1.1.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yaml-language-server@1.20.0: + dependencies: + '@vscode/l10n': 0.0.18 + ajv: 8.18.0 + ajv-draft-04: 1.0.0(ajv@8.18.0) + prettier: 3.8.3 + request-light: 0.5.8 + vscode-json-languageservice: 4.1.8 + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + yaml: 2.7.1 + + yaml@2.7.1: {} + + yaml@2.8.3: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@1.2.2: {} + + yocto-spinner@0.2.3: + dependencies: + yoctocolors: 2.1.2 + + yoctocolors@2.1.2: {} + + zod-to-json-schema@3.25.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod-to-ts@1.2.0(typescript@5.9.3)(zod@3.25.76): + dependencies: + typescript: 5.9.3 + zod: 3.25.76 + + zod@3.25.76: {} + + zod@4.3.6: {} + + zwitch@2.0.4: {} diff --git a/static/_headers b/public/_headers similarity index 84% rename from static/_headers rename to public/_headers index 6121cfd..b1538c0 100644 --- a/static/_headers +++ b/public/_headers @@ -6,7 +6,7 @@ # window that produced 140,800 requests with 39 cache hits (0.028%) and # 775 MB of origin transfer. # -# Docusaurus content-hashes its build output, so those files can be cached +# Astro content-hashes its build output, so those files can be cached # permanently; a new build produces new filenames. # # Cache-Control is deliberately set on specific paths only and never on /*. @@ -18,7 +18,7 @@ X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin -/assets/* +/_astro/* Cache-Control: public, max-age=31536000, immutable /img/* @@ -27,6 +27,12 @@ /sitemap.xml Cache-Control: public, max-age=3600 +/sitemap-index.xml + Cache-Control: public, max-age=3600 + +/sitemap-0.xml + Cache-Control: public, max-age=3600 + /robots.txt Cache-Control: public, max-age=3600 diff --git a/public/fonts/hanken-grotesk-latin.woff2 b/public/fonts/hanken-grotesk-latin.woff2 new file mode 100644 index 0000000..e71775f Binary files /dev/null and b/public/fonts/hanken-grotesk-latin.woff2 differ diff --git a/public/fonts/jetbrains-mono-latin.woff2 b/public/fonts/jetbrains-mono-latin.woff2 new file mode 100644 index 0000000..2ca6ac6 Binary files /dev/null and b/public/fonts/jetbrains-mono-latin.woff2 differ diff --git a/public/fonts/onest-variable.ttf b/public/fonts/onest-variable.ttf new file mode 100644 index 0000000..33bd5a5 Binary files /dev/null and b/public/fonts/onest-variable.ttf differ diff --git a/public/fonts/syne-variable.ttf b/public/fonts/syne-variable.ttf new file mode 100644 index 0000000..2833ae1 Binary files /dev/null and b/public/fonts/syne-variable.ttf differ diff --git a/public/img/apple-touch-icon.png b/public/img/apple-touch-icon.png new file mode 100644 index 0000000..f4cbdef Binary files /dev/null and b/public/img/apple-touch-icon.png differ diff --git a/static/img/balance-logo.svg b/public/img/balance-logo.svg similarity index 100% rename from static/img/balance-logo.svg rename to public/img/balance-logo.svg diff --git a/public/img/banner.png b/public/img/banner.png new file mode 100644 index 0000000..2843637 Binary files /dev/null and b/public/img/banner.png differ diff --git a/public/img/branding/Suites_LogoMark_Black.png b/public/img/branding/Suites_LogoMark_Black.png new file mode 100644 index 0000000..b8a0451 Binary files /dev/null and b/public/img/branding/Suites_LogoMark_Black.png differ diff --git a/public/img/branding/Suites_LogoMark_Black.svg b/public/img/branding/Suites_LogoMark_Black.svg new file mode 100644 index 0000000..6bc10b2 --- /dev/null +++ b/public/img/branding/Suites_LogoMark_Black.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/public/img/branding/Suites_LogoMark_FullColor.png b/public/img/branding/Suites_LogoMark_FullColor.png new file mode 100644 index 0000000..2f79078 Binary files /dev/null and b/public/img/branding/Suites_LogoMark_FullColor.png differ diff --git a/public/img/branding/Suites_LogoMark_FullColor.svg b/public/img/branding/Suites_LogoMark_FullColor.svg new file mode 100644 index 0000000..17f7a09 --- /dev/null +++ b/public/img/branding/Suites_LogoMark_FullColor.svg @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/public/img/branding/Suites_LogoMark_Outline.png b/public/img/branding/Suites_LogoMark_Outline.png new file mode 100644 index 0000000..08db42e Binary files /dev/null and b/public/img/branding/Suites_LogoMark_Outline.png differ diff --git a/public/img/branding/Suites_LogoMark_Outline.svg b/public/img/branding/Suites_LogoMark_Outline.svg new file mode 100644 index 0000000..088b05d --- /dev/null +++ b/public/img/branding/Suites_LogoMark_Outline.svg @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/public/img/branding/Suites_LogoMark_White.png b/public/img/branding/Suites_LogoMark_White.png new file mode 100644 index 0000000..6578890 Binary files /dev/null and b/public/img/branding/Suites_LogoMark_White.png differ diff --git a/public/img/branding/Suites_LogoMark_White.svg b/public/img/branding/Suites_LogoMark_White.svg new file mode 100644 index 0000000..c841371 --- /dev/null +++ b/public/img/branding/Suites_LogoMark_White.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/public/img/branding/Suites_PrimaryLogo_Black copy 2.png b/public/img/branding/Suites_PrimaryLogo_Black copy 2.png new file mode 100644 index 0000000..2665116 Binary files /dev/null and b/public/img/branding/Suites_PrimaryLogo_Black copy 2.png differ diff --git a/public/img/branding/Suites_PrimaryLogo_Black copy 2.svg b/public/img/branding/Suites_PrimaryLogo_Black copy 2.svg new file mode 100644 index 0000000..b27e4a3 --- /dev/null +++ b/public/img/branding/Suites_PrimaryLogo_Black copy 2.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/img/branding/Suites_PrimaryLogo_FullColor.png b/public/img/branding/Suites_PrimaryLogo_FullColor.png new file mode 100644 index 0000000..e5a133f Binary files /dev/null and b/public/img/branding/Suites_PrimaryLogo_FullColor.png differ diff --git a/public/img/branding/Suites_PrimaryLogo_FullColor.svg b/public/img/branding/Suites_PrimaryLogo_FullColor.svg new file mode 100644 index 0000000..47b3fd7 --- /dev/null +++ b/public/img/branding/Suites_PrimaryLogo_FullColor.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/img/branding/Suites_PrimaryLogo_White.png b/public/img/branding/Suites_PrimaryLogo_White.png new file mode 100644 index 0000000..61b8675 Binary files /dev/null and b/public/img/branding/Suites_PrimaryLogo_White.png differ diff --git a/public/img/branding/Suites_PrimaryLogo_White.svg b/public/img/branding/Suites_PrimaryLogo_White.svg new file mode 100644 index 0000000..c5d6a0a --- /dev/null +++ b/public/img/branding/Suites_PrimaryLogo_White.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/img/branding/Suites_Wordmark_Black.png b/public/img/branding/Suites_Wordmark_Black.png new file mode 100644 index 0000000..97a1770 Binary files /dev/null and b/public/img/branding/Suites_Wordmark_Black.png differ diff --git a/public/img/branding/Suites_Wordmark_Black.svg b/public/img/branding/Suites_Wordmark_Black.svg new file mode 100644 index 0000000..ce88ddf --- /dev/null +++ b/public/img/branding/Suites_Wordmark_Black.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/public/img/branding/Suites_Wordmark_FullColor.png b/public/img/branding/Suites_Wordmark_FullColor.png new file mode 100644 index 0000000..d38d379 Binary files /dev/null and b/public/img/branding/Suites_Wordmark_FullColor.png differ diff --git a/public/img/branding/Suites_Wordmark_FullColor.svg b/public/img/branding/Suites_Wordmark_FullColor.svg new file mode 100644 index 0000000..97f9557 --- /dev/null +++ b/public/img/branding/Suites_Wordmark_FullColor.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/public/img/branding/Suites_Wordmark_White.png b/public/img/branding/Suites_Wordmark_White.png new file mode 100644 index 0000000..fb67cc9 Binary files /dev/null and b/public/img/branding/Suites_Wordmark_White.png differ diff --git a/public/img/branding/Suites_Wordmark_White.svg b/public/img/branding/Suites_Wordmark_White.svg new file mode 100644 index 0000000..dd1383e --- /dev/null +++ b/public/img/branding/Suites_Wordmark_White.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/public/img/favicon-16x16.png b/public/img/favicon-16x16.png new file mode 100644 index 0000000..a341874 Binary files /dev/null and b/public/img/favicon-16x16.png differ diff --git a/public/img/favicon.ico b/public/img/favicon.ico new file mode 100644 index 0000000..387c568 Binary files /dev/null and b/public/img/favicon.ico differ diff --git a/public/img/favicon.png b/public/img/favicon.png new file mode 100644 index 0000000..4c97179 Binary files /dev/null and b/public/img/favicon.png differ diff --git a/public/img/favicon.svg b/public/img/favicon.svg new file mode 100644 index 0000000..1b013b5 --- /dev/null +++ b/public/img/favicon.svg @@ -0,0 +1,8 @@ + + Suites + + + + + + diff --git a/static/img/harmonya-logo.svg b/public/img/harmonya-logo.svg similarity index 100% rename from static/img/harmonya-logo.svg rename to public/img/harmonya-logo.svg diff --git a/static/img/inversify-logo.png b/public/img/inversify-logo.png similarity index 100% rename from static/img/inversify-logo.png rename to public/img/inversify-logo.png diff --git a/static/img/jest-logo.png b/public/img/jest-logo.png similarity index 100% rename from static/img/jest-logo.png rename to public/img/jest-logo.png diff --git a/static/img/lemonade-logo.svg b/public/img/lemonade-logo.svg similarity index 100% rename from static/img/lemonade-logo.svg rename to public/img/lemonade-logo.svg diff --git a/public/img/logo.png b/public/img/logo.png new file mode 100644 index 0000000..70ad5ba Binary files /dev/null and b/public/img/logo.png differ diff --git a/public/img/logo.svg b/public/img/logo.svg new file mode 100644 index 0000000..17f7a09 --- /dev/null +++ b/public/img/logo.svg @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/static/img/monday-logo.svg b/public/img/monday-logo.svg similarity index 100% rename from static/img/monday-logo.svg rename to public/img/monday-logo.svg diff --git a/static/img/nestjs-logo.png b/public/img/nestjs-logo.png similarity index 100% rename from static/img/nestjs-logo.png rename to public/img/nestjs-logo.png diff --git a/static/img/sinon-logo.png b/public/img/sinon-logo.png similarity index 100% rename from static/img/sinon-logo.png rename to public/img/sinon-logo.png diff --git a/public/img/suites-logo.png b/public/img/suites-logo.png new file mode 100644 index 0000000..8ec540b Binary files /dev/null and b/public/img/suites-logo.png differ diff --git a/static/img/vetric.svg b/public/img/vetric.svg similarity index 100% rename from static/img/vetric.svg rename to public/img/vetric.svg diff --git a/static/img/vitest-logo.png b/public/img/vitest-logo.png similarity index 100% rename from static/img/vitest-logo.png rename to public/img/vitest-logo.png diff --git a/public/llms.txt b/public/llms.txt new file mode 100644 index 0000000..0d2ff45 --- /dev/null +++ b/public/llms.txt @@ -0,0 +1,45 @@ +# Suites + +> Suites is a unit testing framework for TypeScript backends working with inversion of control and dependency injection. It generates mocks for class dependencies and provides a flat test container that constructs classes without bootstrapping your application framework, so you can write solitary or sociable unit tests with minimal boilerplate. + +Suites supports NestJS 10/11/12 and Inversify 7/8, plus InjectionJS ^2.6.1 starting in Suites 4.1. It integrates with Jest and Vitest and offers migration guides from Automock and Suites 3. Node.js requirements: ^22.22.2 || ^24.15.0 || ^26.0.0. CommonJS and ESM are supported. InjectionJS supports constructor injection, not contextual inject() calls or production provider/container configuration. + +## Get started + +- [Get Started overview](https://suites.dev/docs/get-started/): entry point for new users +- [Why Suites](https://suites.dev/docs/get-started/why-suites): the problem Suites solves and how it differs from manual mocking +- [Installation](https://suites.dev/docs/get-started/installation): install the right packages for your DI framework and test runner +- [Quick Start](https://suites.dev/docs/get-started/quickstart): choose a NestJS, InversifyJS, or InjectionJS walkthrough using Jest or Vitest + +## Guides + +- [Guides overview](https://suites.dev/docs/guides/): index of conceptual and how-to guides +- [Fundamentals](https://suites.dev/docs/guides/fundamentals): core concepts and the testing model +- [Solitary tests](https://suites.dev/docs/guides/solitary): isolating a single class with auto-generated mocks +- [Sociable tests](https://suites.dev/docs/guides/sociable): testing a class together with selected real collaborators +- [NestJS](https://suites.dev/docs/guides/nestjs/): service tests, custom injection tokens, and selected real collaborators +- [InversifyJS](https://suites.dev/docs/guides/inversify/): symbol tokens, named lookups, and sociable tests +- [InjectionJS](https://suites.dev/docs/guides/injectionjs/): constructor injection, InjectionToken overrides, Vitest module identity, and support boundaries +- [Test doubles](https://suites.dev/docs/guides/test-doubles): mocks, stubs, and spies in Suites +- [How Suites works](https://suites.dev/docs/guides/how-suites-works/): how Suites builds an isolated DI graph for each test + +## API reference + +- [API Reference index](https://suites.dev/docs/api-reference/) +- [TestBed (solitary)](https://suites.dev/docs/api-reference/testbed-solitary) +- [TestBed (sociable)](https://suites.dev/docs/api-reference/testbed-sociable) +- [Mock](https://suites.dev/docs/api-reference/mock) +- [Mock configuration](https://suites.dev/docs/api-reference/mock-configuration) +- [Unit reference](https://suites.dev/docs/api-reference/unit-reference) +- [Types](https://suites.dev/docs/api-reference/types) + +## Migration + +- [Migrating from Suites 3 to 4](https://suites.dev/docs/migration-guides/from-v3/) +- [Migrating from Automock](https://suites.dev/docs/migration-guides/from-automock) + +## Project + +- [Changelog](https://suites.dev/docs/changelog) +- [GitHub repository](https://github.com/suites-dev/unit) +- [Examples repository](https://github.com/suites-dev/suites-unit-examples) diff --git a/static/robots.txt b/public/robots.txt similarity index 90% rename from static/robots.txt rename to public/robots.txt index 0a16aaf..fc095ba 100644 --- a/static/robots.txt +++ b/public/robots.txt @@ -15,8 +15,7 @@ User-agent: DuckDuckBot Allow: / # =========================================== -# AI crawlers: explicitly allowed so Suites -# shows up in AI-generated answers. +# AI crawlers: explicitly allowed. # =========================================== User-agent: OAI-SearchBot Allow: / @@ -68,4 +67,4 @@ Disallow: / User-agent: serpstatbot Disallow: / -Sitemap: https://suites.dev/sitemap.xml +Sitemap: https://suites.dev/sitemap-index.xml diff --git a/public/video.mp4 b/public/video.mp4 new file mode 100644 index 0000000..305a98a Binary files /dev/null and b/public/video.mp4 differ diff --git a/scripts/algolia-reindex.mjs b/scripts/algolia-reindex.mjs index c5e77fc..d23cacf 100644 --- a/scripts/algolia-reindex.mjs +++ b/scripts/algolia-reindex.mjs @@ -1,55 +1,22 @@ #!/usr/bin/env node -/** - * Triggers a re-crawl of the Algolia DocSearch crawler after a Netlify build. - * - * Runs only on production deploys. Skips silently for deploy previews, - * branch deploys, and local builds. Skips with a warning if credentials - * are missing instead of failing the build. - * - * Required environment variables (set in Netlify → Site settings → Environment): - * ALGOLIA_CRAWLER_USER_ID - * ALGOLIA_CRAWLER_API_KEY - * ALGOLIA_CRAWLER_ID (the crawler UUID, currently 7ecdfe3f-744f-4633-9dd4-bc7c721b383b) - */ +import { pathToFileURL } from 'node:url'; -const CONTEXT = process.env.CONTEXT ?? "local"; -const USER_ID = process.env.ALGOLIA_CRAWLER_USER_ID; -const API_KEY = process.env.ALGOLIA_CRAWLER_API_KEY; -const CRAWLER_ID = process.env.ALGOLIA_CRAWLER_ID; - -if (CONTEXT !== "production") { - console.log(`[algolia-reindex] Skipping: CONTEXT=${CONTEXT} (production only).`); - process.exit(0); -} - -if (!USER_ID || !API_KEY || !CRAWLER_ID) { - console.warn( - "[algolia-reindex] Skipping: missing ALGOLIA_CRAWLER_USER_ID / " + - "ALGOLIA_CRAWLER_API_KEY / ALGOLIA_CRAWLER_ID. Set them in Netlify env." - ); - process.exit(0); -} - -const auth = Buffer.from(`${USER_ID}:${API_KEY}`).toString("base64"); -const url = `https://crawler.algolia.com/api/1/crawlers/${CRAWLER_ID}/reindex`; - -try { - const res = await fetch(url, { - method: "POST", - headers: { Authorization: `Basic ${auth}` }, +// The Netlify onSuccess hook calls this after publishing, never during a build. +export async function reindexCrawler() { + const user = process.env.ALGOLIA_CRAWLER_USER_ID; + const key = process.env.ALGOLIA_CRAWLER_API_KEY; + const id = process.env.ALGOLIA_CRAWLER_ID; + if (!user || !key || !id) throw new Error('Missing Algolia crawler credentials'); + const response = await fetch(`https://crawler.algolia.com/api/1/crawlers/${id}/reindex`, { + method: 'POST', + headers: { Authorization: `Basic ${Buffer.from(`${user}:${key}`).toString('base64')}` }, + signal: AbortSignal.timeout(20000), }); + if (!response.ok) throw new Error(`Crawler reindex failed: ${response.status} ${response.statusText}`); + console.log('[algolia-reindex] Reindex triggered after publication.'); +} - if (!res.ok) { - const body = await res.text(); - console.error( - `[algolia-reindex] Failed: ${res.status} ${res.statusText}\n${body}` - ); - // Don't fail the deploy. Search staleness is not a build blocker. - process.exit(0); - } - - console.log("[algolia-reindex] Reindex triggered successfully."); -} catch (err) { - console.error("[algolia-reindex] Network error:", err.message); - process.exit(0); +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + if (process.env.CONTEXT !== 'production') console.log('[algolia-reindex] Skipping outside production.'); + else await reindexCrawler(); } diff --git a/scripts/check-build.mjs b/scripts/check-build.mjs new file mode 100644 index 0000000..e1f4b48 --- /dev/null +++ b/scripts/check-build.mjs @@ -0,0 +1,95 @@ +import { readFile, readdir } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = fileURLToPath(new URL('../', import.meta.url)); +const dist = path.join(root, 'dist'); +const origin = 'https://suites.dev'; +const errors = []; +const pages = new Map(); +let references = 0; +let links = 0; + +const decode = value => value.replace(/&/g, '&').replace(/"/g, '"') + .replace(/'/g, "'").replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n))); +function attributes(tag) { + return Object.fromEntries([...tag.matchAll(/([\w:-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)] + .map(match => [match[1].toLowerCase(), decode(match[2] ?? match[3])])); +} +async function files(directory) { + const entries = await readdir(directory, { withFileTypes: true }); + return (await Promise.all(entries.map(entry => entry.isDirectory() + ? files(path.join(directory, entry.name)) : path.join(directory, entry.name)))).flat(); +} +const output = await files(dist); +const available = new Set(output.map(file => '/' + path.relative(dist, file).split(path.sep).join('/'))); +function asset(url, base, owner) { + if (!url || url.startsWith('#') || /^(?:data:|blob:)/i.test(url)) return; + const target = new URL(decode(url), new URL(base, origin)); + if (target.origin !== origin) return; + references++; + if (!available.has(decodeURIComponent(target.pathname))) errors.push(`${owner}: missing asset ${target.pathname}`); +} +for (const file of output.filter(file => file.endsWith('.html'))) { + const relative = '/' + path.relative(dist, file).split(path.sep).join('/'); + const route = relative.endsWith('/index.html') ? relative.slice(0, -10) : relative; + const html = await readFile(file, 'utf8'); + // Markdown copy payloads and inline scripts are text, not DOM references. + const markup = html.replace(/]*)>[\s\S]*?<\/script>/gi, ''); + const ids = new Set(); + const hrefs = []; + for (const match of markup.matchAll(/<([a-z][\w:-]*)\b[^>]*>/gi)) { + const tag = match[1].toLowerCase(); + const attr = attributes(match[0]); + if (attr.id) { + if (ids.has(attr.id)) errors.push(`${route}: duplicate id ${attr.id}`); + ids.add(attr.id); + } + if (tag === 'a' && attr.href) hrefs.push(attr.href); + if (['script', 'img', 'source', 'video', 'audio'].includes(tag)) asset(attr.src, route, route); + if (tag === 'video') asset(attr.poster, route, route); + if (tag === 'link' && ['stylesheet', 'preload', 'modulepreload', 'icon', 'apple-touch-icon'].includes(attr.rel)) { + asset(attr.href, route, route); + } + if (['img', 'source'].includes(tag) && attr.srcset && !attr.srcset.startsWith('data:')) { + for (const source of attr.srcset.split(',')) asset(source.trim().split(/\s+/)[0], route, route); + } + } + const analytics = html.includes('https://www.googletagmanager.com/gtag/js'); + if (analytics !== (process.env.CONTEXT === 'production')) errors.push(`${route}: analytics must run only in production`); + pages.set(route, { ids, hrefs }); +} +for (const file of output.filter(file => file.endsWith('.css'))) { + const relative = '/' + path.relative(dist, file).split(path.sep).join('/'); + const css = await readFile(file, 'utf8'); + for (const match of css.matchAll(/url\(\s*(['"]?)([^)'"\s]+)\1\s*\)/g)) asset(match[2], relative, relative); +} +for (const [route, page] of pages) { + for (const href of page.hrefs) { + if (/^(?:mailto:|tel:|javascript:|data:)/i.test(href)) continue; + const target = new URL(href, new URL(route, origin)); + if (target.origin !== origin) continue; + const pathname = decodeURIComponent(target.pathname); + if (available.has(pathname) && !pathname.endsWith('.html')) continue; + const targetRoute = pathname.endsWith('.html') || pathname.endsWith('/') ? pathname : pathname + '/'; + const destination = pages.get(targetRoute); + links++; + if (!destination) errors.push(`${route}: missing linked page ${href}`); + else if (target.hash && !destination.ids.has(decodeURIComponent(target.hash.slice(1)))) { + errors.push(`${route}: missing linked anchor ${href}`); + } + } +} +const legacy = JSON.parse(await readFile(path.join(root, 'src/config/legacy-anchors.json'), 'utf8')); +let aliases = 0; +for (const page of legacy) { + const route = `/docs/${page.slug}/`; + for (const { old, target } of page.aliases) { + aliases++; + if (!pages.get(route)?.ids.has(old)) errors.push(`${route}: missing legacy fragment #${old}`); + if (target && !pages.get(route)?.ids.has(target)) errors.push(`${route}: missing legacy destination #${target}`); + } +} +for (const error of errors) console.error(`Error: ${error}`); +console.log(`${pages.size} pages, ${references} asset references, ${links} internal links, ${aliases} legacy fragments, ${errors.length} errors`); +if (errors.length) process.exitCode = 1; diff --git a/scripts/check-seo.mjs b/scripts/check-seo.mjs new file mode 100644 index 0000000..d290775 --- /dev/null +++ b/scripts/check-seo.mjs @@ -0,0 +1,199 @@ +import { readdir, readFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// Audit rendered output, including metadata that is shared across layouts. +const root = fileURLToPath(new URL('../', import.meta.url)); +const dist = path.join(root, 'dist'); +const site = 'https://suites.dev'; +const preview = process.argv.includes('--preview') || ['deploy-preview', 'branch-deploy'].includes(process.env.CONTEXT ?? ''); +const errors = []; +const warnings = []; +const indexed = new Set(); +const titles = new Map(); +const descriptions = new Map(); +const imageCache = new Map(); +let pages = 0; + +const decode = (value = '') => value + .replace(/&#x([\da-f]+);/gi, (_, n) => String.fromCodePoint(parseInt(n, 16))) + .replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n))) + .replace(/"/g, '"').replace(/'|'/g, "'") + .replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); + +function attributes(tag) { + const attrs = {}; + for (const match of tag.matchAll(/([\w:-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/g)) { + attrs[match[1].toLowerCase()] = decode(match[2] ?? match[3] ?? match[4]); + } + return attrs; +} + +async function files(directory) { + const entries = await readdir(directory, { withFileTypes: true }); + const nested = await Promise.all(entries.map((entry) => entry.isDirectory() + ? files(path.join(directory, entry.name)) : [path.join(directory, entry.name)])); + return nested.flat(); +} + +function duplicate(map, value, route, label) { + if (map.has(value)) errors.push(`${route}: ${label} duplicates ${map.get(value)}`); + else map.set(value, route); +} + +async function localFile(url) { + const parsed = new URL(url, site); + if (parsed.origin !== site) throw new Error(`unexpected asset origin ${parsed.origin}`); + const relative = decodeURIComponent(parsed.pathname).replace(/^\//, ''); + const file = path.resolve(dist, relative); + if (!file.startsWith(`${dist}${path.sep}`)) throw new Error('asset escapes output directory'); + return readFile(file); +} + +async function checkImage(url, width, height, type) { + if (new URL(url).origin !== site) { + if (!imageCache.has(url)) warnings.push(`social image hosted externally; verify dimensions and availability: ${url}`); + imageCache.set(url, null); + return; + } + if (!imageCache.has(url)) imageCache.set(url, await localFile(url)); + const bytes = imageCache.get(url); + if (bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) { + if (type !== 'image/png') throw new Error('PNG social card must declare image/png'); + if (Number(width) !== bytes.readUInt32BE(16) || Number(height) !== bytes.readUInt32BE(20)) { + throw new Error('social-image dimensions do not match the PNG'); + } + } +} + +const output = await files(dist).catch(() => { + throw new Error('Build the site before running check:seo.'); +}); + +for (const file of output.filter((file) => file.endsWith('.html'))) { + pages++; + const relative = path.relative(dist, file).split(path.sep).join('/'); + const route = relative === 'index.html' ? '/' + : relative.endsWith('/index.html') ? `/${relative.slice(0, -10)}` : `/${relative}`; + const expectedUrl = new URL(route, site).href; + const html = await readFile(file, 'utf8'); + const head = html.match(/]*>([\s\S]*?)<\/head>/i)?.[1] ?? ''; + const meta = new Map(); + const check = (condition, message) => { if (!condition) errors.push(`${route}: ${message}`); }; + for (const tag of head.matchAll(/]*>/gi)) { + const attr = attributes(tag[0]); + const key = attr.name ?? attr.property; + if (!key) continue; + check(!meta.has(key), `duplicate ${key} tag`); + meta.set(key, attr.content ?? ''); + } + const pageTitles = [...head.matchAll(/]*>([\s\S]*?)<\/title>/gi)]; + const title = decode(pageTitles[0]?.[1] ?? ''); + const description = meta.get('description') ?? ''; + check(pageTitles.length === 1 && title.trim(), 'expected one nonempty document title'); + check(description.trim(), 'missing description'); + check(title.split(/\s*\|\s*/).filter((part) => part === 'Suites').length <= 1, 'repeated site name in title'); + check(attributes(html.match(/]*>/i)?.[0] ?? '').lang === 'en', 'missing English language declaration'); + check((html.match(/]*>/gi)].map((match) => attributes(match[0])) + .filter((link) => link.rel === 'canonical'); + if (statusPage) check(canonicals.length === 0, '404 page must not advertise a canonical'); + else { + check(canonicals.length === 1 && canonicals[0].href === expectedUrl, 'canonical must be the production URL with a trailing slash'); + check(meta.get('og:url') === expectedUrl, 'og:url differs from the canonical URL'); + } + + const image = meta.get('og:image'); + check(image?.startsWith('https://'), 'social card must have an absolute HTTPS URL'); + check(meta.get('og:image:secure_url') === image && meta.get('twitter:image') === image, 'social-image URLs differ'); + check(meta.get('og:image:alt') && meta.get('twitter:image:alt') === meta.get('og:image:alt'), 'missing or inconsistent social-image alt text'); + if (image) { + try { await checkImage(image, meta.get('og:image:width'), meta.get('og:image:height'), meta.get('og:image:type')); } + catch (error) { errors.push(`${route}: ${error.message}`); } + } + for (const link of [...head.matchAll(/]*>/gi)].map((match) => attributes(match[0]))) { + if (link.href?.startsWith('/') && ['icon', 'shortcut icon', 'apple-touch-icon', 'preload'].includes(link.rel)) { + try { await localFile(link.href); } catch (error) { errors.push(`${route}: missing head asset ${link.href}`); } + } + } + + const schemas = [...head.matchAll(/]*)>([\s\S]*?)<\/script>/gi)] + .filter((match) => attributes(match[1]).type === 'application/ld+json'); + if (noIndex) check(schemas.length === 0, 'noindex pages should omit search structured data'); + else { + indexed.add(expectedUrl); + duplicate(titles, title, route, 'title'); + duplicate(descriptions, description, route, 'description'); + if (title.length > 70) warnings.push(`${route}: review long title (${title.length} characters)`); + if (description.length < 70 || description.length > 180) warnings.push(`${route}: review description length (${description.length} characters)`); + check(schemas.length === 1, 'expected one structured-data graph'); + try { + const data = JSON.parse(schemas[0]?.[2] ?? 'null'); + check(data?.['@context'] === 'https://schema.org' && Array.isArray(data?.['@graph']), 'invalid structured-data graph'); + const graph = data?.['@graph'] ?? []; + for (const type of ['Organization', 'WebSite']) check(graph.some((node) => node['@type'] === type), `missing ${type} schema`); + const isArticle = route.startsWith('/docs/') && route !== '/docs/'; + const type = isArticle ? 'TechArticle' : 'WebPage'; + const page = graph.find((node) => node['@type'] === type); + check(page?.url === expectedUrl && page?.name === title && page?.description === description, 'page schema differs from metadata'); + check(meta.get('og:type') === (isArticle ? 'article' : 'website'), 'incorrect Open Graph page type'); + if (isArticle) { + const breadcrumbs = graph.find((node) => node['@type'] === 'BreadcrumbList')?.itemListElement ?? []; + check(breadcrumbs.length >= 2, 'missing breadcrumb hierarchy'); + check(breadcrumbs.at(-1)?.item === expectedUrl, 'last breadcrumb differs from canonical'); + for (const [index, item] of breadcrumbs.entries()) { + check(item.position === index + 1 && item.name, 'invalid breadcrumb position or label'); + const target = new URL(item.item); + check(target.origin === site && target.pathname.endsWith('/'), 'breadcrumb URL must be production and slash-terminated'); + await readFile(path.join(dist, target.pathname, 'index.html')); + } + } + } catch (error) { errors.push(`${route}: structured-data validation failed: ${error.message}`); } + } +} + +const sitemapFiles = output.filter((file) => /sitemap.*\.xml$/.test(file)); +const sitemapUrls = new Set(); +for (const file of sitemapFiles) { + const xml = await readFile(file, 'utf8'); + if (!xml.includes('([^<]+)<\/loc>/g)) sitemapUrls.add(decode(match[1])); +} +for (const url of indexed) if (!sitemapUrls.has(url)) errors.push(`sitemap: missing indexable URL ${url}`); +for (const url of sitemapUrls) if (!indexed.has(url)) errors.push(`sitemap: unexpected or noindex URL ${url}`); +if (!preview && indexed.size === 0) errors.push('production build has no indexable pages'); + +const robots = await readFile(path.join(dist, 'robots.txt'), 'utf8'); +const advertised = [...robots.matchAll(/^Sitemap:\s*(\S+)/gm)]; +if (!advertised.length) errors.push('robots.txt: missing sitemap URL'); +for (const [, url] of advertised) { + try { + if (!url.startsWith(`${site}/`)) throw new Error('sitemap must use production HTTPS origin'); + if (!preview) await localFile(url); + } catch (error) { errors.push(`robots.txt: ${error.message}`); } +} +const llms = await readFile(path.join(dist, 'llms.txt'), 'utf8'); +for (const match of llms.matchAll(/\]\((https:\/\/suites\.dev\/[^)#]*)[^)]*\)/g)) { + const url = new URL(match[1]); + const target = path.join(dist, url.pathname, 'index.html'); + try { await readFile(target); } catch { errors.push(`llms.txt: missing route ${url.pathname}`); } +} + +for (const warning of warnings) console.warn(`Review: ${warning}`); +for (const error of errors) console.error(`Error: ${error}`); +console.log(`${pages} pages checked, ${indexed.size} indexable, ${sitemapUrls.size} sitemap URLs, ${errors.length} errors, ${warnings.length} review notes${preview ? ' (preview mode)' : ''}`); +if (errors.length) process.exitCode = 1; diff --git a/scripts/esm-dependencies.mjs b/scripts/esm-dependencies.mjs new file mode 100644 index 0000000..f91ffd2 --- /dev/null +++ b/scripts/esm-dependencies.mjs @@ -0,0 +1,34 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { dirname, join } from 'node:path'; + +// Resolve from each package's own dependency graph, including pnpm links. +// CommonJS packages stay external so Astro's module runner can load them. +export function esmDependencies(name, from) { + const bundled = new Set(); + const visited = new Set(); + const pending = [{ name, from }]; + while (pending.length) { + const dependency = pending.pop(); + let entry; + try { entry = createRequire(dependency.from).resolve(dependency.name); } + catch { continue; } // Type-only packages have no executable entry point. + let directory = dirname(entry); + while (directory !== dirname(directory)) { + const manifest = join(directory, 'package.json'); + if (existsSync(manifest)) { + const data = JSON.parse(readFileSync(manifest, 'utf8')); + if (data.name) { + if (!visited.has(manifest)) { + visited.add(manifest); + if (data.type === 'module') bundled.add(data.name); + for (const child of Object.keys(data.dependencies ?? {})) pending.push({ name: child, from: manifest }); + } + break; + } + } + directory = dirname(directory); + } + } + return [...bundled]; +} diff --git a/scripts/sync-algolia-config.mjs b/scripts/sync-algolia-config.mjs index b28b20f..942934b 100644 --- a/scripts/sync-algolia-config.mjs +++ b/scripts/sync-algolia-config.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node /** - * Pushes config/algolia-config.js up to the Algolia DocSearch crawler so the + * Pushes config/algolia-config.mjs up to the Algolia DocSearch crawler so the * dashboard config matches what's in the repo. Run manually after editing the * crawler config in this repo: * @@ -12,30 +12,13 @@ * Pass --dry-run to print the JSON payload without sending it. */ -import { createRequire } from "node:module"; -import { fileURLToPath } from "node:url"; -import { dirname, resolve } from "node:path"; +import local from '../config/algolia-config.mjs'; +import { pathToFileURL } from 'node:url'; -const require = createRequire(import.meta.url); -const __dirname = dirname(fileURLToPath(import.meta.url)); - -const USER_ID = process.env.ALGOLIA_CRAWLER_USER_ID; -const API_KEY = process.env.ALGOLIA_CRAWLER_API_KEY; -const CRAWLER_ID = process.env.ALGOLIA_CRAWLER_ID; const DRY_RUN = process.argv.includes("--dry-run"); -if (!USER_ID || !API_KEY || !CRAWLER_ID) { - console.error( - "Missing required env: ALGOLIA_CRAWLER_USER_ID, ALGOLIA_CRAWLER_API_KEY, ALGOLIA_CRAWLER_ID" - ); - process.exit(1); -} - -const configPath = resolve(__dirname, "../config/algolia-config.js"); -const local = require(configPath); - // Convert the local config to the Crawler API shape: -// - top-level appId/apiKey stay +// - crawler-owned appId/apiKey are omitted to preserve its write credentials // - any `recordExtractor` function gets serialized as { __type, source } // Notes on intentionally omitted fields: // - `externalUrlRegex`: rejected by the API as a forbidden field. URL @@ -44,7 +27,7 @@ const local = require(configPath); // by the docs frontend, not the *write* key the crawler needs to index. // The crawler already has the correct write key configured in the // dashboard. Leave it untouched. -const payload = { +export const payload = { rateLimit: local.rateLimit, maxDepth: local.maxDepth, startUrls: local.startUrls, @@ -60,29 +43,27 @@ const payload = { })), }; -if (DRY_RUN) { - console.log(JSON.stringify(payload, null, 2)); - process.exit(0); +export async function syncCrawlerConfig() { + const USER_ID = process.env.ALGOLIA_CRAWLER_USER_ID; + const API_KEY = process.env.ALGOLIA_CRAWLER_API_KEY; + const CRAWLER_ID = process.env.ALGOLIA_CRAWLER_ID; + if (!USER_ID || !API_KEY || !CRAWLER_ID) throw new Error('Missing Algolia crawler credentials'); + const auth = Buffer.from(`${USER_ID}:${API_KEY}`).toString('base64'); + const url = `https://crawler.algolia.com/api/1/crawlers/${CRAWLER_ID}/config`; + const res = await fetch(url, { + method: "PATCH", + headers: { + Authorization: `Basic ${auth}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + signal: AbortSignal.timeout(20000), + }); + if (!res.ok) throw new Error(`Crawler config sync failed: ${res.status} ${res.statusText}`); + console.log('Crawler config synced successfully.'); } -const auth = Buffer.from(`${USER_ID}:${API_KEY}`).toString("base64"); -const url = `https://crawler.algolia.com/api/1/crawlers/${CRAWLER_ID}/config`; - -const res = await fetch(url, { - method: "PATCH", - headers: { - Authorization: `Basic ${auth}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(payload), -}); - -const body = await res.text(); - -if (!res.ok) { - console.error(`Failed: ${res.status} ${res.statusText}\n${body}`); - process.exit(1); +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + if (DRY_RUN) console.log(JSON.stringify(payload, null, 2)); + else await syncCrawlerConfig(); } - -console.log("Crawler config synced successfully."); -console.log(body); diff --git a/src/components/AskAI/index.tsx b/src/components/AskAI/index.tsx deleted file mode 100644 index b9a45ce..0000000 --- a/src/components/AskAI/index.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { useLocation } from '@docusaurus/router'; -import { FaRobot, FaComments } from 'react-icons/fa'; -import { SiOpenai } from 'react-icons/si'; -import { trackButtonClick } from '@site/src/utils/analytics'; -import styles from './styles.module.css'; - -interface AskAIButtonProps { - className?: string; - position?: 'navbar' | 'doc' | 'floating'; - provider?: 'chatgpt' | 'claude' | 'both'; -} - -export default function AskAIButton({ - className = '', - position = 'floating', - provider = 'both' -}: AskAIButtonProps) { - const location = useLocation(); - const [pageTitle, setPageTitle] = useState(''); - const [showOptions, setShowOptions] = useState(false); - - useEffect(() => { - // Extract page content and metadata - const extractPageInfo = () => { - // Get the page title from document or h1 - const h1Element = document.querySelector('article h1'); - const docTitle = h1Element?.textContent || document.title.replace(' | Suites', ''); - setPageTitle(docTitle); - }; - - // Wait for content to load - setTimeout(extractPageInfo, 100); - }, [location.pathname]); - - const buildPrompt = () => { - const currentUrl = window.location.href; - - // Build a concise, effective prompt - return `I'm reading the Suites testing framework documentation, this is the page title "${pageTitle}", and the url is ${currentUrl} - read the page and answer the following questions:`; - }; - - const handleAskChatGPT = () => { - trackButtonClick('Ask AI - ChatGPT', position); - const prompt = buildPrompt(); - // ChatGPT supports the q parameter with optional model selection - const chatGPTUrl = `https://chat.openai.com/?model=gpt-4o&q=${encodeURIComponent(prompt)}`; - window.open(chatGPTUrl, '_blank'); - setShowOptions(false); - }; - - const handleAskClaude = () => { - trackButtonClick('Ask AI - Claude', position); - const prompt = buildPrompt(); - // Claude uses the q parameter on the /new endpoint - const claudeUrl = `https://claude.ai/new?q=${encodeURIComponent(prompt)}`; - window.open(claudeUrl, '_blank'); - setShowOptions(false); - }; - - const handleClick = () => { - if (provider === 'chatgpt') { - handleAskChatGPT(); - } else if (provider === 'claude') { - handleAskClaude(); - } else { - setShowOptions(!showOptions); - } - }; - - const buttonContent = ( - <> - - Ask AI - - ); - - if (position === 'floating') { - return ( -
- - - {provider === 'both' && showOptions && ( -
- - -
- )} -
- ); - } - - return ( -
- - - {provider === 'both' && showOptions && ( -
- - -
- )} -
- ); -} \ No newline at end of file diff --git a/src/components/AskAI/styles.module.css b/src/components/AskAI/styles.module.css deleted file mode 100644 index 103ff38..0000000 --- a/src/components/AskAI/styles.module.css +++ /dev/null @@ -1,207 +0,0 @@ -.askAIButton { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 8px 16px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - border: none; - border-radius: 8px; - cursor: pointer; - font-size: 14px; - font-weight: 500; - transition: all 0.3s ease; - box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); -} - -.askAIButton:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(102, 126, 234, 0.5); - background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); -} - -.askAIButton:active { - transform: translateY(0); -} - -.askAIButton.navbar { - margin-left: 12px; - padding: 6px 12px; - font-size: 13px; -} - -.askAIButton.doc { - margin-top: 16px; - margin-bottom: 16px; -} - -.askAIButtonFloating { - position: relative; - display: flex; - align-items: center; - gap: 8px; - padding: 12px 20px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - border: none; - border-radius: 24px; - cursor: pointer; - font-size: 14px; - font-weight: 500; - transition: all 0.3s ease; - box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4); -} - -.askAIButtonFloating:hover { - transform: translateY(-3px); - box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6); - background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); -} - -.icon { - font-size: 18px; -} - -.text { - font-weight: 500; -} - -/* Dark mode adjustments */ -[data-theme='dark'] .askAIButton, -[data-theme='dark'] .askAIButtonFloating { - background: linear-gradient(135deg, #ef4779 0%, #f06292 100%); - box-shadow: 0 2px 8px rgba(239, 71, 121, 0.3); -} - -[data-theme='dark'] .askAIButton:hover, -[data-theme='dark'] .askAIButtonFloating:hover { - background: linear-gradient(135deg, #f06292 0%, #ef4779 100%); - box-shadow: 0 4px 12px rgba(239, 71, 121, 0.5); -} - -/* Container for button and options */ -.askAIContainer { - position: relative; - display: inline-block; -} - -/* When container is fixed positioned (for floating button) */ -.askAIContainer:has(.askAIButtonFloating) { - position: fixed; - bottom: 24px; - right: 24px; - z-index: 1000; -} - -/* Options menu for floating button */ -.optionsMenu { - position: absolute; - bottom: calc(100% + 16px); - right: 0; - display: flex; - flex-direction: column; - gap: 8px; - padding: 8px; - background: var(--ifm-background-surface-color); - border-radius: 12px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); - min-width: 140px; - animation: slideUp 0.2s ease; - z-index: 1001; -} - -/* Options menu for inline button */ -.optionsMenuInline { - position: absolute; - top: calc(100% + 8px); - right: 0; - display: flex; - flex-direction: column; - gap: 8px; - padding: 8px; - background: var(--ifm-background-surface-color); - border-radius: 12px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); - min-width: 140px; - animation: slideDown 0.2s ease; - z-index: 10; -} - -.optionButton { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 12px; - background: transparent; - color: var(--ifm-font-color-base); - border: 1px solid var(--ifm-border-color); - border-radius: 8px; - cursor: pointer; - font-size: 14px; - transition: all 0.2s ease; - white-space: nowrap; -} - -.optionButton:hover { - background: var(--ifm-color-primary-lightest); - border-color: var(--ifm-color-primary); - transform: translateX(2px); -} - -.optionIcon { - font-size: 16px; -} - -/* Animations */ -@keyframes slideUp { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes slideDown { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Mobile responsive */ -@media (max-width: 768px) { - .askAIButtonFloating { - bottom: 16px; - right: 16px; - padding: 10px 16px; - font-size: 13px; - } - - .askAIButtonFloating .text { - display: none; - } - - .askAIButtonFloating { - width: 48px; - height: 48px; - padding: 0; - border-radius: 50%; - justify-content: center; - } - - .askAIButtonFloating .icon { - margin: 0; - } - - .optionsMenu { - bottom: calc(100% + 4px); - right: -20px; - } -} \ No newline at end of file diff --git a/src/components/ClientOnly/index.tsx b/src/components/ClientOnly/index.tsx deleted file mode 100644 index 4efecd5..0000000 --- a/src/components/ClientOnly/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, { useEffect, useState } from 'react'; - -interface ClientOnlyProps { - children: React.ReactNode; - fallback?: React.ReactNode; -} - -export default function ClientOnly({ children, fallback = null }: ClientOnlyProps) { - const [hasMounted, setHasMounted] = useState(false); - - useEffect(() => { - setHasMounted(true); - }, []); - - if (!hasMounted) { - return <>{fallback}; - } - - return <>{children}; -} \ No newline at end of file diff --git a/src/components/Footer/index.tsx b/src/components/Footer/index.tsx deleted file mode 100644 index ad5e955..0000000 --- a/src/components/Footer/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; -import Link from "@docusaurus/Link"; -import { FaGithub } from "react-icons/fa"; -import styles from "./styles.module.css"; - -export default function Footer(): JSX.Element { - return ( -
-
-
- - - -
-

- Released under the MIT License -
- Copyright © 2025 -{" "} - - Omer Morad - -

-
-
- ); -} diff --git a/src/components/Footer/styles.module.css b/src/components/Footer/styles.module.css deleted file mode 100644 index 7ece850..0000000 --- a/src/components/Footer/styles.module.css +++ /dev/null @@ -1,32 +0,0 @@ -/* Footer CSS */ -.pageFooter { - background-color: #242526; - text-align: center; - color: rgba(204, 204, 204, 0.61); - font-family: "Inter", sans-serif; - padding: 20px 0; - font-weight: bold; -} - -.pageFooter .icon { - color: #c2c3c3; - font-size: 24px; - margin: 0 10px; -} - -.pageFooter .icon:hover { - color: #ffffff; -} - -.pageFooter p { - font-size: 14px; - font-weight: 500; - line-height: 26px; - color: rgba(204, 204, 204, 0.61); -} - -.pageFooter p a { - color: var(--ifm-color-primary); - font-weight: 600; -} -/* Footer section CSS */ \ No newline at end of file diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx deleted file mode 100644 index 6497274..0000000 --- a/src/components/Header/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, {useEffect, useState} from 'react'; -import Link from '@docusaurus/Link'; -import { FaGithub } from "react-icons/fa"; -import { FaXTwitter } from "react-icons/fa6"; -import styles from './styles.module.css'; - -export default function Header(): JSX.Element { - const [scrolled, setScrolled] = useState(false); - - useEffect(() => { - const handleScroll = () => { - const isScrolled = window.scrollY > 50; // Change 50 to your desired scroll threshold - if (isScrolled !== scrolled) { - setScrolled(isScrolled); - } - }; - - window.addEventListener('scroll', handleScroll); - - // Cleanup the event listener on a component unmount - return () => { - window.removeEventListener('scroll', handleScroll); - }; - }, [scrolled]); - return ( - <> -
-
-
-
- - Suites Logo - -
-
    -
  • - Docs -
  • -
  • - - - -
  • -
-
-
-
-
-
-
- - Suites Logo Suites - -
- -
- -
-
-
- Suites Logo -

Suites (formerly Automock)

-

A progressive testing meta-framework

- -
-
-
-
- - ); -} diff --git a/src/components/Header/styles.module.css b/src/components/Header/styles.module.css deleted file mode 100644 index c6c5451..0000000 --- a/src/components/Header/styles.module.css +++ /dev/null @@ -1,313 +0,0 @@ -/* Header CSS */ -.navbarSticky { - position: fixed; - z-index: 100000; - left: 0; - right: 0; - height: 60px; - box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.07); - background: var(--ifm-background-color); - padding: 8px 0; - opacity: 0; - transform: translate3d(0, -100%, 0); - -webkit-transform: translate3d(0, -100%, 0); - transition: all 180ms ease-in-out; - -ms-transition: all 180ms ease-in-out; - -webkit-transition: all 180ms ease-in-out; -} - -.navbarSticky.visible { - opacity: 1; - transform: translate3d(0, 0, 0); - -webkit-transform: translate3d(0, 0, 0); -} - -.navbarSticky .navbar { - display: flex; - align-items: center; - justify-content: space-between; -} - -.logoLink { - display: flex; - align-items: center; - color: var(--ifm-background-color); - font-weight: 500; -} - -.logoLink span { - margin-left: 10px; - display: block; -} - -.navbarSticky .logoWrapper img { - width: 40px; - max-height: 40px; -} - -.navbarSticky .navWrapper { - margin: 0; - padding: 0; - max-width: calc(100% - 50px); - text-align: right; - vertical-align: middle; - display: flex; - align-items: center; - list-style-type: none; -} - -.navbar-sticky .navWrapper li { - display: inline-block; - vertical-align: middle; -} - -.navbarSticky .navWrapper a { - color: var(--ifm-color-primary-darkest); - /* color: var(--ifm-color-primary); */ - font-size: 14px; - margin-left: 20px; - font-weight: 700; - transition: all .1s ease-in; - display: flex; - align-items: center; -} - -.navbarSticky .navWrapper a:hover { - color: var(--ifm-color-primary); -} - -.navbarSticky .navWrapper .icon { - margin-left: 12px; - font-size: 16px; -} - -.new { - color: #fff; - background: var(--ifm-color-primary); - font-size: 11px; - line-height: 1.2; - padding: 2px 4px; - border-radius: 2px; - margin-right: 2px; - display: inline-block; -} - -@media (max-width: 1020px) { - .hidden_md { - display: none !important; - } -} - -@media (max-width: 920px) { - .hidden_sm { - display: none !important; - } - - .navbarSticky { - display: none; - } -} - -@media (max-width: 640px) { - .hidden_xs { - display: none !important; - } -} - -.pageHeader { - font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; - /*background-image: url("../../../static/img/header.1af6756a.png");*/ - background-position: center top; - background-size: cover; - background-repeat: no-repeat; - background-color: #23272f; - background-attachment: fixed; - color: #fff; - min-height: 620px; -} - -.subWrapper { - display: flex; - width: 100%; - justify-content: space-between; - padding: 20px 30px; -} - -.topWrapper { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; -} - -.subWrapper img, -.subWrapper svg, -.logoWrapper svg, -.logoWrapper img { - max-width: 36px; -} - -.navWrapper a { - font-weight: bold; - color: #fff; - font-size: 16px; - margin-left: 25px; - transition: all 100ms ease-in; - -moz-transition: all 100ms ease-in; - -webkit-transition: all 100ms ease-in; -} - -.navWrapper a:hover { - color: var(--ifm-color-primary); -} - -.navWrapper li { - display: inline-block; - vertical-align: middle; -} - -.navWrapper .icon { - margin-left: 27px; - font-size: 22px; -} - -.mainWrapper { - width: 100%; - margin-top: 40px; - display: flex; - flex-direction: column; - align-items: center; - text-align: center; -} - -.mainWrapper img.mainLogo { - display: block; - width: 120px; -} - -.mainWrapper h1 { - font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; - font-weight: 800; - font-size: 2.25rem; /* 36px - Match Next.js h1 */ - line-height: 1.111; - margin-top: 20px; - padding: 10px 0; -} - -.mainWrapper h1 span { - display: block; - font-size: 20px; -} - -.mainWrapper h4 { - font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; - font-weight: 400; - max-width: 650px; - margin: 0; - font-size: 20px; - line-height: 28px; -} - -.buttonsWrapper { - margin: 40px 0; -} - -.buttonsWrapper .btn:first-child { - margin-right: 20px; -} - -.buttonsWrapper .btn .fa-github { - margin-right: 6px; - font-size: 18px; - vertical-align: middle; -} - -.btn { - font-weight: 700; - font-size: 16px; - color: #fff; - text-align: center; - padding: 12px 24px; - min-width: 120px; - border-radius: 30px; - transition: all 100ms ease-in; - display: inline-block; -} - -.btn img { - margin-right: 5px; -} - -.btn span { - vertical-align: middle; -} - -.btn.documentationBtn { - background: var(--ifm-color-primary); - border: 2px solid var(--ifm-color-primary); - color: #fff; -} - -.btn.documentationBtn:hover { - border: 2px solid var(--ifm-color-primary); - background: transparent; - color: #fff; -} - -.btn.sourceBtn { - border: 2px solid #fff; - color: #fff; -} - -.btn.sourceBtn:hover { - border: 2px solid var(--ifm-color-primary); - color: var(--ifm-color-primary); -} - -.buttonsWrapper .btn svg { - margin-right: 6px; - font-size: 18px; - vertical-align: middle; -} - -@media (max-width: 991px) { - .mainWrapper { - text-align: center; - } - - .main-wrapper h4 { - margin-right: auto; - margin-left: auto; - } - - .buttonsWrapper .btn, - .buttonsWrapper .btn:first-child { - margin: 0 10px; - } -} - -@media (max-width: 920px) { - .pageHeader { - background-position: 35% bottom; - } -} - -@media (max-width: 640px) { - .mainWrapper { - padding-top: 100px; - } - - .buttonsWrapper .btn, - .buttonsWrapper .btn:first-child { - display: block; - max-width: 300px; - margin-left: auto; - margin-right: auto; - } - - .buttonsWrapper .btn:first-child { - margin-bottom: 20px; - } -} - -/* Header CSS */ diff --git a/src/components/HomepageFeatures/index.tsx b/src/components/HomepageFeatures/index.tsx deleted file mode 100644 index 1d3e5d8..0000000 --- a/src/components/HomepageFeatures/index.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React from "react"; -import styles from "./styles.module.css"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faAreaChart, - faArrowRight, - faArrowUpWideShort, - IconDefinition, -} from "@fortawesome/free-solid-svg-icons"; - -type FeatureItem = { - title: string; - description: JSX.Element; - icon: IconDefinition; - link: { - ref: string; - text: string; - }; -}; - -const FeatureList: FeatureItem[] = [ - { - title: "Zero-Setup, Automatic Mocking", - description: ( - <> -

- Automatically generate mock objects, eliminate manual setup and reduce - boilerplate code of your unit tests -

- - ), - icon: faAreaChart, - link: { - ref: "/docs/guides/", - text: "Developer Guide", - }, - }, - { - title: "Scale Your Test Suites", - description: ( - <> -

- Suites' flexible architecture supports projects of all sizes, from - small microservices to large monoliths -

- - ), - icon: faArrowUpWideShort, - link: { - ref: "/docs/get-started/", - text: "Getting Started", - }, - }, -]; - -function Feature({ title, description, icon, link }: FeatureItem) { - return ( -
-
- -

{title}

-
-

{description}

- -
- ); -} - -export function HomepageFeatures(): JSX.Element { - return ( -
-
-
- {FeatureList.map((props, idx) => ( -
- {" "} - {/* Ensure class matches your CSS */} - -
- ))} -
-
-
- ); -} - -export function ExploreLink(): JSX.Element { - return ( - - ); -} diff --git a/src/components/HomepageFeatures/styles.module.css b/src/components/HomepageFeatures/styles.module.css deleted file mode 100644 index 242ddb3..0000000 --- a/src/components/HomepageFeatures/styles.module.css +++ /dev/null @@ -1,82 +0,0 @@ -.features { - width: 100%; /* Ensure the features section spans the full width */ - max-width: 98%; - margin-top: 20px; - padding: 0; /* Remove padding around the section */ - display: flex; - flex-wrap: wrap; - margin-bottom: 20px; -} - -.feature { - margin: 8px; - font-family: "Inter", sans-serif; - padding: 20px 10px; - min-height: 250px; - background: linear-gradient(18deg, rgba(7, 7, 7, 0.325) 52.75%, var(--ifm-color-primary) 180%); - text-align: left; - display: flex; /* Added for aligning the title and description horizontally */ - flex-direction: column; /* Stack title and description vertically */ - justify-content: space-evenly; - border-radius: 8px; - /* Remove any border that could be causing lines */ - border: none; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); -} - -.feature h2 { - margin: 0; - border: none; - letter-spacing: 0.35px; - font-size: 1.8rem; -} - -.description { - font-size: 1rem; - padding: 20px; - padding-bottom: 10px; -} - -.row { - display: flex; - width: 100%; /* Ensures the row spans the full width */ - margin: 0; /* Eliminate space between rows */ -} - -.features .col { - margin: 0; - padding: 0; -} - -@media (min-width: 768px) { /* Adjust breakpoint as necessary */ - .col--6 { - padding: 0; - } -} - -.buttons { - text-align: right; /* Aligns the button to the right */ -} - -.button { - margin-right: 14px; -} - -.heading { - padding-left: 20px; - display: flex; - align-items: center; -} - -.icon { - background: rgba(239, 71, 121, .3); - padding: 20px; - margin-right: 20px; - border-radius: 5px; - font-size: 1.8rem; - display: block; - /* Remove any border or outline that might be causing lines */ - border: none; - outline: none; - box-shadow: none; -} \ No newline at end of file diff --git a/src/components/NotFoundContent/index.tsx b/src/components/NotFoundContent/index.tsx deleted file mode 100644 index 9c8dc1e..0000000 --- a/src/components/NotFoundContent/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from "react"; -import Link from "@docusaurus/Link"; -import styles from "./styles.module.css"; - -const SUGGESTIONS: Array<{ label: string; description: string; to: string }> = [ - { - label: "Get Started", - description: "Install Suites and write your first test.", - to: "/docs/get-started/", - }, - { - label: "Quickstart", - description: "A minimal end-to-end example.", - to: "/docs/get-started/quickstart", - }, - { - label: "Guides", - description: "Solitary, sociable, test doubles, and fundamentals.", - to: "/docs/guides/", - }, - { - label: "API Reference", - description: "TestBed, Mock, and configuration APIs.", - to: "/docs/api-reference/", - }, - { - label: "Migrating from Automock", - description: "Move an existing Automock setup to Suites.", - to: "/docs/migration-guides/from-automock", - }, -]; - -export default function NotFoundContent(): React.ReactElement { - return ( -
-
-

404

-

This page took an early return.

-

- We may have moved or renamed it during the recent docs refresh. - Try one of these instead: -

- -
    - {SUGGESTIONS.map((item) => ( -
  • - - {item.label} - {item.description} - -
  • - ))} -
- -

- Still stuck? Go to the homepage or{" "} - - open an issue on GitHub - - . -

-
-
- ); -} diff --git a/src/components/NotFoundContent/styles.module.css b/src/components/NotFoundContent/styles.module.css deleted file mode 100644 index 53d71d9..0000000 --- a/src/components/NotFoundContent/styles.module.css +++ /dev/null @@ -1,86 +0,0 @@ -.container { - display: flex; - align-items: center; - justify-content: center; - padding: 6rem 1.5rem; - min-height: 60vh; -} - -.inner { - max-width: 720px; - width: 100%; - text-align: left; -} - -.code { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", monospace; - font-size: 1rem; - letter-spacing: 0.15em; - color: var(--ifm-color-emphasis-600); - margin-bottom: 1rem; - text-transform: uppercase; -} - -.title { - font-size: 2.25rem; - line-height: 1.2; - margin: 0 0 1rem; -} - -.subtitle { - font-size: 1.05rem; - color: var(--ifm-color-emphasis-700); - margin-bottom: 2.5rem; -} - -.list { - list-style: none; - padding: 0; - margin: 0 0 2.5rem; - display: grid; - grid-template-columns: 1fr; - gap: 0.75rem; -} - -@media (min-width: 640px) { - .list { - grid-template-columns: 1fr 1fr; - } -} - -.item { - margin: 0; -} - -.itemLink { - display: flex; - flex-direction: column; - gap: 0.25rem; - padding: 1rem 1.25rem; - border: 1px solid var(--ifm-color-emphasis-300); - border-radius: 0.5rem; - text-decoration: none; - color: inherit; - transition: border-color 0.15s ease, transform 0.15s ease; -} - -.itemLink:hover { - border-color: var(--ifm-color-primary); - text-decoration: none; - transform: translateY(-1px); -} - -.itemLabel { - font-weight: 600; - font-size: 1rem; -} - -.itemDescription { - font-size: 0.9rem; - color: var(--ifm-color-emphasis-700); -} - -.footer { - font-size: 0.95rem; - color: var(--ifm-color-emphasis-700); -} diff --git a/src/components/docs/Breadcrumb.astro b/src/components/docs/Breadcrumb.astro new file mode 100644 index 0000000..5482595 --- /dev/null +++ b/src/components/docs/Breadcrumb.astro @@ -0,0 +1,45 @@ +--- +/* + * Docs breadcrumb. Uses the Quick Start navigation hierarchy for framework + * paths and the URL hierarchy for other pages. + * + * Shape: Section / Subsection / ... / Current Page + * + * No "Docs" prefix, no "Home" prefix; both are tautological. The topbar's + * `Suites / docs` brand mark already signals "you are in docs". The breadcrumb + * starts at the section so the first item is always a useful jump. + * + * Separator is a thin slash character (not an SVG chevron) so it lines up + * cleanly with the text baseline at 13px and matches the existing brand-mark + * "Suites / docs" pattern in the header. + * + * Intermediate segments are humanized from the slug (`effect-coverage` -> `Effect coverage`, + * only first word capitalised so multi-word labels do not become Title Case). + * The final segment uses the page's frontmatter title verbatim so it matches the h1. + */ + +import { docsBreadcrumbs } from '@/lib/breadcrumbs'; + +interface Props { + slug: string; + title?: string; +} + +const { slug, title } = Astro.props; + +const items = docsBreadcrumbs(slug, title); +--- + diff --git a/src/components/docs/ClassDependenciesExample.astro b/src/components/docs/ClassDependenciesExample.astro new file mode 100644 index 0000000..b001cc7 --- /dev/null +++ b/src/components/docs/ClassDependenciesExample.astro @@ -0,0 +1,99 @@ +--- +import Tabs from '@/components/mdx/Tabs.astro'; +import TabItem from '@/components/mdx/TabItem.astro'; +import { Code } from 'astro-expressive-code/components'; + +interface Props { + example?: 'greeting' | 'email'; +} +const { example: exampleType = 'greeting' } = Astro.props; + +const frameworks = [ + { label: 'NestJS', value: 'nestjs', module: '@nestjs/common', decorator: 'Injectable', inject: '' }, + { label: 'InversifyJS', value: 'inversify', module: 'inversify', decorator: 'injectable', inject: 'inject' }, + { label: 'InjectionJS', value: 'injectionjs', module: 'injection-js', decorator: 'Injectable', inject: '' }, +]; + +function example(framework: typeof frameworks[number]) { + const { decorator, inject } = framework; + if (exampleType === 'email') { + const tokenDecorator = inject || 'Inject'; + const validator = inject ? '@inject(EmailValidator) ' : ''; + return `import 'reflect-metadata'; +import { ${decorator}, ${tokenDecorator} } from '${framework.module}'; + +export interface SavedUser { + id: number; + email: string; +} + +export interface DatabaseClient { + users: { + save(user: { email: string }): Promise; + }; +} + +@${decorator}() +export class EmailValidator { + isValid(email: string): boolean { + return email.includes('@') && email.includes('.'); + } +} + +@${decorator}() +export class UserService { + constructor( + ${validator}private readonly validator: EmailValidator, + @${tokenDecorator}('DATABASE') private readonly database: DatabaseClient, + ) {} + + async createUser(email: string): Promise { + if (!this.validator.isValid(email)) { + throw new Error('Invalid email'); + } + return this.database.users.save({ email }); + } +}`; + } + const imports = inject ? `${decorator}, ${inject}` : decorator; + const repository = inject ? `@${inject}(UserRepository) ` : ''; + const formatter = inject ? `@${inject}(NameFormatter) ` : ''; + return `import 'reflect-metadata'; +import { ${imports} } from '${framework.module}'; + +@${decorator}() +export class UserRepository { + async findName(id: string): Promise { + throw new Error(\`Database access is not configured for \${id}\`); + } +} + +@${decorator}() +export class NameFormatter { + format(name: string): string { + return name.trim(); + } +} + +@${decorator}() +export class UserService { + constructor( + ${repository}private readonly repository: UserRepository, + ${formatter}private readonly formatter: NameFormatter, + ) {} + + async greet(id: string): Promise { + const name = await this.repository.findName(id); + return \`Hello, \${this.formatter.format(name)}!\`; + } +}`; +} +--- + + + {frameworks.map((framework, index) => ( + + + + ))} + diff --git a/src/components/docs/ContainerComparisonDiagram.astro b/src/components/docs/ContainerComparisonDiagram.astro new file mode 100644 index 0000000..34a110e --- /dev/null +++ b/src/components/docs/ContainerComparisonDiagram.astro @@ -0,0 +1,94 @@ +--- +interface Props { + framework?: 'nestjs' | 'inversify' | 'injectionjs'; +} + +const { framework } = Astro.props; +const nativeNames = { + nestjs: 'NestJS TestingModule', + inversify: 'InversifyJS Container', + injectionjs: 'ReflectiveInjector', +}; +const nativeName = framework ? nativeNames[framework] : 'Framework container'; +const id = `container-comparison-${crypto.randomUUID()}`; +--- + +
+
+ + Framework IoC container and Suites TestBed + Two construction paths for the same UserService. The framework container uses registered providers and its own factories and resolution rules. Its test configuration can register real dependencies or mocks. Suites reads injection metadata, creates dependency mocks, and constructs UserService. Sociable tests can expose selected real classes. Suites does not load the framework's provider registrations. Both approaches can use mocks. + + + + + + + + Framework IoC container + + + + + + + + Registrations + Modules / bindings / providers + + {nativeName} + Factories + resolution rules + + UserService + real class + + Real dependency + + Mock provider + Exercises the registrations included in this test. + + + Suites TestBed + + + + + + + + Injection metadata + Constructor types + tokens + + Suites TestBed + Generate mocks + construct + + UserService + real class + + Auto mock + + Exposed class + Mocks by default; selected real classes with sociable().expose(). + +
+
Both approaches can use mocks. Native containers resolve test registrations; Suites builds the class from injection metadata. Solid dependency boxes are real; dashed boxes are mocks.
+
+ + diff --git a/src/components/docs/DependencyDecisionDiagram.astro b/src/components/docs/DependencyDecisionDiagram.astro new file mode 100644 index 0000000..987b828 --- /dev/null +++ b/src/components/docs/DependencyDecisionDiagram.astro @@ -0,0 +1,44 @@ +--- +const id = `dependency-choice-${crypto.randomUUID()}`; +--- +
+ + Choose how each injected dependency behaves + If a dependency performs external I/O, keep it mocked or supply a test double. If its real behavior belongs in the test, expose a supported class after checking its dependencies for I/O. Leave other dependencies mocked, or supply explicit values with mock and final. These are reader decisions; Suites does not detect I/O. + + + 01 + External I/O? + Keep the dependency mocked + or supply a test double. + + + + + 02 + Need its real behavior? + Select a supported class with + .expose(Class). Check its + dependencies for I/O too. + + + + 03 + Other dependencies? + Leave mocked by default. + For supplied values, use + .mock(token).final(value). + + +
Choose the boundary explicitly. Injection tokens identify dependencies; they do not tell Suites whether code performs I/O.
+
+ diff --git a/src/components/docs/ImportedDependencyDiagram.astro b/src/components/docs/ImportedDependencyDiagram.astro new file mode 100644 index 0000000..37e927b --- /dev/null +++ b/src/components/docs/ImportedDependencyDiagram.astro @@ -0,0 +1,62 @@ +--- +const id = `imports-${crypto.randomUUID()}`; +--- + +
+
+ + Injected dependencies and direct imports + The real service receives an injected repository mock. Its directly imported sendEmail function still runs and can call an external email service. TestBed does not intercept this import. + + + + + + + + + + + + Service under test + REAL IMPLEMENTATION + Constructor injection + + Repository + MOCK + Replaced by TestBed + Direct import + + sendEmail() + REAL FUNCTION + Can call external I/O + + Email service + EXTERNAL I/O + +
+
TestBed replaces injected dependencies. Direct imports still run unless you replace them separately.
+
+ + diff --git a/src/components/docs/LastUpdated.astro b/src/components/docs/LastUpdated.astro new file mode 100644 index 0000000..e12f771 --- /dev/null +++ b/src/components/docs/LastUpdated.astro @@ -0,0 +1,23 @@ +--- +/* + * Small "last updated" stamp at the bottom of the article, before the prev/next pager. + * Reads an ISO date string from frontmatter. If absent the component renders nothing + * (the layout decides whether to call this or skip it). + */ + +interface Props { + iso: string; +} + +const { iso } = Astro.props; + +function fmt(s: string): string { + const d = new Date(s); + if (Number.isNaN(d.getTime())) return s; + return d.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); +} +--- +

+ Last updated + +

diff --git a/src/components/docs/PageActions.astro b/src/components/docs/PageActions.astro new file mode 100644 index 0000000..36a744f --- /dev/null +++ b/src/components/docs/PageActions.astro @@ -0,0 +1,116 @@ +--- +/* + * Docs page-actions cluster. + * + * Three affordances, dark-mode-first: + * 1. Copy Markdown -- copies the page's raw markdown body to the clipboard so + * the reader can paste it into an LLM, an issue, or their notes. The body + * is rendered as a hidden + + diff --git a/src/components/docs/PrevNext.astro b/src/components/docs/PrevNext.astro new file mode 100644 index 0000000..fcd22cf --- /dev/null +++ b/src/components/docs/PrevNext.astro @@ -0,0 +1,48 @@ +--- +import sidebar, { productFromPath } from '@/config/sidebar'; + +interface Props { + slug: string; +} +const { slug } = Astro.props; + +const currentHref = `/docs/${slug}`.replace(/\/$/, ''); +const product = productFromPath(`${currentHref}/`); +const groups = sidebar[product]; +const allItems = groups.flatMap((group) => group.items); +const group = groups.find((group) => group.items.some((item) => item.href.replace(/\/$/, '') === currentHref)); +const items = group?.items ?? []; + +const idx = items.findIndex((item) => item.href.replace(/\/$/, '') === currentHref); + +const current = items[idx]; +const sequential = group?.sequential !== false; +const prev = current?.previous !== undefined + ? allItems.find((item) => item.href === current.previous) + : sequential && idx > 0 ? items[idx - 1] : null; +const next = current?.next !== undefined + ? allItems.find((item) => item.href === current.next) + : sequential && idx >= 0 && idx < items.length - 1 ? items[idx + 1] : null; +--- +{(prev || next) && ( + +)} diff --git a/src/components/docs/Sidebar.astro b/src/components/docs/Sidebar.astro new file mode 100644 index 0000000..2917e3b --- /dev/null +++ b/src/components/docs/Sidebar.astro @@ -0,0 +1,67 @@ +--- +import sidebar, { productFromPath } from '@/config/sidebar'; + +const currentPath = Astro.url.pathname.replace(/\/$/, '') || '/'; +const activeProduct = productFromPath(Astro.url.pathname); +const groups = sidebar[activeProduct]; + +function isGroupActive(items: { href: string }[]) { + return items.some((item) => { + const h = item.href.replace(/\/$/, ''); + return currentPath === h; + }); +} +--- + + + diff --git a/src/components/docs/TOC.astro b/src/components/docs/TOC.astro new file mode 100644 index 0000000..7ff2a49 --- /dev/null +++ b/src/components/docs/TOC.astro @@ -0,0 +1,163 @@ +--- +interface Heading { + depth: number; + text: string; + slug: string; +} +interface Props { + headings: Heading[]; +} +const { headings } = Astro.props; +const filtered = headings.filter((h) => h.depth >= 2 && h.depth <= 4); +--- +{filtered.length > 0 && ( +
    + {filtered.map((h) => ( +
  • + {h.text} +
  • + ))} +
+)} + + diff --git a/src/components/docs/TestingModeComparison.astro b/src/components/docs/TestingModeComparison.astro new file mode 100644 index 0000000..b3bc319 --- /dev/null +++ b/src/components/docs/TestingModeComparison.astro @@ -0,0 +1,21 @@ +--- +import TestingModeDiagram from './TestingModeDiagram.astro'; +--- +
+
+

Solitary

+

Only the unit runs real code.

+ +
+
+

Sociable

+

Selected collaborators run real code.

+ +
+
+ diff --git a/src/components/docs/TestingModeDiagram.astro b/src/components/docs/TestingModeDiagram.astro new file mode 100644 index 0000000..21902c3 --- /dev/null +++ b/src/components/docs/TestingModeDiagram.astro @@ -0,0 +1,61 @@ +--- +interface Props { + mode: 'solitary' | 'sociable'; + caption?: string; +} +const { mode, caption } = Astro.props; +const sociable = mode === 'sociable'; +const id = `testing-mode-${crypto.randomUUID()}`; +--- +
+
+ + {sociable ? 'Sociable' : 'Solitary'} test boundary + From left to right, a test calls the real UserService, which depends on EmailValidator and DATABASE. {sociable ? 'EmailValidator runs real code because the test exposes it.' : 'EmailValidator is mocked.'} DATABASE stays mocked. The outlined area encloses real implementations exercised. All mocks still run inside the test process. + + + + + + {sociable ? ( + + ) : ( + + )} + Real implementations exercised + + + + + + + Test + + UserService + real unit + + EmailValidator + {sociable ? 'real collaborator' : 'mock'} + + 'DATABASE' + mock + +
+ {caption &&
{caption}
} +
+ diff --git a/src/components/docs/UnitReferenceDiagram.astro b/src/components/docs/UnitReferenceDiagram.astro new file mode 100644 index 0000000..a3a2c5b --- /dev/null +++ b/src/components/docs/UnitReferenceDiagram.astro @@ -0,0 +1,66 @@ +--- +const id = `unit-reference-${crypto.randomUUID()}`; +const dependencies = [ + { label: 'Auto mock', kind: 'mock' }, + { label: '.impl() mock', kind: 'mock' }, + { label: '.final() supplied', kind: 'supplied' }, + { label: '.expose() real', kind: 'real' }, +]; +--- + +
+
+ + Injected dependencies and retrievable mocks + The real unit receives auto mocks, .impl() mocks, supplied .final() values, and exposed real classes. unitRef.get() can look up only auto mocks and .impl() mocks. Exposed classes apply to sociable tests. + + + + + + + 1. Injected into the unit + + unit + Real class + {dependencies.map((dependency, index) => ( + + + + {dependency.label} + + ))} + + 2. Available through lookup + + unitRef.get() + {[{ label: 'Auto mock', y: 354 }, { label: '.impl() mock', y: 412 }].map(({ label, y }) => ( + + + + {label} + + ))} + No lookup: .final() values or exposed classes. + +
+
Solid arrows show injection; dotted arrows show lookup. Dashed boxes are configurable mocks. Exposed real classes apply to sociable tests.
+
+ + diff --git a/src/components/home/LovableCodeComparison.astro b/src/components/home/LovableCodeComparison.astro new file mode 100644 index 0000000..a7e607f --- /dev/null +++ b/src/components/home/LovableCodeComparison.astro @@ -0,0 +1,282 @@ +--- +import { Code } from 'astro-expressive-code/components'; + +const withoutSuitesCode = `import { UserService } from './user-service'; +import { UserApi } from './user-api'; +import { Database } from './database'; +import { Logger } from './logger'; + +let userApi: jest.Mocked; +let database: jest.Mocked; +let logger: jest.Mocked; +let userService: UserService; + +beforeEach(() => { + userApi = { + getRandom: jest.fn(), + findById: jest.fn(), + } as jest.Mocked; + + database = { + saveUser: jest.fn(), + deleteUser: jest.fn(), + } as jest.Mocked; + + logger = { + info: jest.fn(), + error: jest.fn(), + } as jest.Mocked; + + userService = new UserService(userApi, database, logger); +}); + +it('generates and saves a random user', async () => { + userApi.getRandom.mockResolvedValue({ id: 1, name: 'Ada' }); + await userService.generateRandomUser(); + expect(database.saveUser).toHaveBeenCalled(); +});`; + +const withSuitesCode = `import { TestBed } from '@suites/unit'; + +const { unit, unitRef } = await TestBed + .solitary(UserService) + .compile(); + +const userApi = unitRef.get(UserApi); +const database = unitRef.get(Database); + +it('generates and saves a random user', async () => { + userApi.getRandom.mockResolvedValue({ id: 1, name: 'Ada' }); + await unit.generateRandomUser(); + expect(database.saveUser).toHaveBeenCalled(); +});`; + +const withoutLines = withoutSuitesCode.split('\n').length; +const withLines = withSuitesCode.split('\n').length; +--- + +
+
+
+ UserService.test.ts + +
+ + {withoutLines} lines + + +
+ + +
+
+
+ +
+ +
+ + +
+
+ + + + diff --git a/src/components/home/LovableHomepage.astro b/src/components/home/LovableHomepage.astro new file mode 100644 index 0000000..cc6410a --- /dev/null +++ b/src/components/home/LovableHomepage.astro @@ -0,0 +1,614 @@ +--- +import referenceHtml from '../../../loveable.html?raw'; +import referenceCss from '../../../loveable.css?raw'; + +const pageStart = referenceHtml.indexOf('
', scrollGlowStart) + '
'.length; + +if (scrollGlowStart === -1 || scrollGlowEnd < ''.length) { + throw new Error('Could not find the Lovable ambient background glow inside loveable.html'); +} + +homepageMarkup = `${homepageMarkup.slice(0, scrollGlowStart)}${homepageMarkup.slice(scrollGlowEnd)}`; + +const headerStart = homepageMarkup.indexOf('', headerStart) + ''.length; + +if (headerStart === -1 || headerEnd < ''.length) { + throw new Error('Could not find the Lovable homepage header inside loveable.html'); +} + +const topHeader = ` +
+
+ + + + + + +
+ + + + + + + +
+
+
`; + +homepageMarkup = `${homepageMarkup.slice(0, headerStart)}${topHeader}${homepageMarkup.slice(headerEnd)}`; + +const heroStart = homepageMarkup.indexOf('', heroStart) + ''.length; + +if (heroStart === -1 || heroEnd < ''.length) { + throw new Error('Could not find the Lovable hero inside loveable.html'); +} + +const topHero = ` +
+
+
+
+

+ Project Suites. Make unit testing clear for TypeScript projects built on dependency injection. +

+

+ An open-source testing project for TypeScript. Suites Unit is stable and in production use. +

+ +
+
+
+
`; + +homepageMarkup = `${homepageMarkup.slice(0, heroStart)}${topHero}${homepageMarkup.slice(heroEnd)}`; + +const retainedHeroEnd = homepageMarkup.indexOf('', heroStart) + ''.length; + +if (retainedHeroEnd < ''.length) { + throw new Error('Could not retain the rebuilt hero inside the Lovable homepage'); +} + +homepageMarkup = `${homepageMarkup.slice(0, retainedHeroEnd)}\n`; + +const localFontFaces = ` +@font-face { + font-family: "Hanken Grotesk"; + src: url('/fonts/hanken-grotesk-latin.woff2') format('woff2'); + font-style: normal; + font-weight: 400 700; + font-display: swap; +} +@font-face { + font-family: "JetBrains Mono"; + src: url('/fonts/jetbrains-mono-latin.woff2') format('woff2'); + font-style: normal; + font-weight: 400 700; + font-display: swap; +} +`; + +const homepageStyles = `${localFontFaces}${referenceCss + .replace( + '/__l5e/assets-v1/16a891b8-0750-41ef-a9f6-95a83cb89458/Onest-VariableFont_wght.ttf', + '/fonts/onest-variable.ttf', + ) + .replace( + '/__l5e/assets-v1/45907748-7e1c-47da-9513-dba7dc3e186a/Syne-VariableFont_wght.ttf', + '/fonts/syne-variable.ttf', + )}`; +--- + + + +
+ + + +
+ + + + diff --git a/src/components/home/LovableTechnicalFigure.astro b/src/components/home/LovableTechnicalFigure.astro new file mode 100644 index 0000000..f9c202d --- /dev/null +++ b/src/components/home/LovableTechnicalFigure.astro @@ -0,0 +1,174 @@ +--- +import referenceHtml from '../../../loveable.html?raw'; + +const heading = 'Type-Safe, Automatic Mocking'; +const headingAt = referenceHtml.indexOf(heading); +const sectionStart = referenceHtml.lastIndexOf('', headingAt) + ''.length; + +if (headingAt === -1 || sectionStart === -1 || sectionEnd < ''.length) { + throw new Error('Could not find the selected Lovable technical figure'); +} + +let figureMarkup = referenceHtml.slice(sectionStart, sectionEnd); + +figureMarkup = figureMarkup + .replace( + '
', + '
', + ) + .replace( + '
', + '
', + ) + .replace( + 'class="order-2 lg:order-1"', + 'class="lovable-figure-visual"', + ) + .replace( + 'class="order-1 text-center lg:order-2 lg:text-left"', + 'class="lovable-figure-copy"', + ) + .replace( + ']*>[\s\S]*?<\/p>/, + '

Suites Unit reads the dependency graph, creates typed test doubles, and gives the test direct access to both the unit and its collaborators. The setup stays close to the behavior being verified.

', + ) + .replace( + //, + 'See the Unit guides ', + ) + .replace(/\sdata-tsd-source="[^"]*"/g, ''); +--- + +
+ +
+ + diff --git a/src/components/islands/AskAI.tsx b/src/components/islands/AskAI.tsx new file mode 100644 index 0000000..a12b390 --- /dev/null +++ b/src/components/islands/AskAI.tsx @@ -0,0 +1,238 @@ +import React, { useCallback, useEffect, useId, useRef, useState } from 'react'; +import { FaRobot, FaComments } from 'react-icons/fa'; +import { SiOpenai } from 'react-icons/si'; +import { trackButtonClick } from '@/utils/analytics'; + +interface AskAIButtonProps { + className?: string; + position?: 'navbar' | 'doc' | 'floating'; + provider?: 'chatgpt' | 'claude' | 'both'; +} + +/* + * Ask AI island. + * + * The "both" mode opens a small popover with two provider choices. We treat the + * popover as a real menu: + * - role="menu" on the list, role="menuitem" on each option + * - aria-haspopup / aria-expanded / aria-controls on the trigger + * - Escape closes and returns focus to the trigger + * - Outside pointerdown closes (does not steal focus) + * - First item is focused on open so keyboard users can act immediately + * - Arrow Down / Arrow Up cycle through menuitems + * + * For "chatgpt" / "claude" the button is a direct action (opens a new tab), so + * we skip all the popover state. + * + * The page title is read from the rendered
heading, falling back to + * document.title. We use a microtask + a resilient lookup instead of an + * arbitrary setTimeout (the previous 100ms timer was racy on slow hydration). + */ +export default function AskAIButton({ + className = '', + position = 'floating', + provider = 'both', +}: AskAIButtonProps) { + const [pageTitle, setPageTitle] = useState(''); + const [open, setOpen] = useState(false); + + const triggerRef = useRef(null); + const menuRef = useRef(null); + const menuId = useId(); + + // Extract title once on mount. We poll a couple of frames in case the + // article heading hydrates after this island. + useEffect(() => { + let cancelled = false; + let attempts = 0; + + const read = () => { + if (cancelled) return; + const h1 = document.querySelector('article h1'); + const docTitle = + h1?.textContent?.trim() || + document.title.replace(' | Suites', '').trim(); + if (docTitle) { + setPageTitle(docTitle); + return; + } + if (attempts++ < 5) requestAnimationFrame(read); + }; + + read(); + return () => { + cancelled = true; + }; + }, []); + + const buildPrompt = useCallback(() => { + const currentUrl = typeof window !== 'undefined' ? window.location.href : ''; + return `I'm reading the Suites testing framework documentation, this is the page title "${pageTitle}", and the url is ${currentUrl} + read the page and answer the following questions:`; + }, [pageTitle]); + + const closeMenu = useCallback( + (returnFocus = true) => { + setOpen(false); + if (returnFocus) { + // Defer to next tick so React unmount-then-focus does not race. + requestAnimationFrame(() => triggerRef.current?.focus()); + } + }, + [], + ); + + const handleAskChatGPT = useCallback(() => { + trackButtonClick('Ask AI - ChatGPT', position); + const chatGPTUrl = `https://chat.openai.com/?model=gpt-4o&q=${encodeURIComponent(buildPrompt())}`; + window.open(chatGPTUrl, '_blank', 'noopener,noreferrer'); + closeMenu(); + }, [buildPrompt, position, closeMenu]); + + const handleAskClaude = useCallback(() => { + trackButtonClick('Ask AI - Claude', position); + const claudeUrl = `https://claude.ai/new?q=${encodeURIComponent(buildPrompt())}`; + window.open(claudeUrl, '_blank', 'noopener,noreferrer'); + closeMenu(); + }, [buildPrompt, position, closeMenu]); + + const handleClick = () => { + if (provider === 'chatgpt') return handleAskChatGPT(); + if (provider === 'claude') return handleAskClaude(); + setOpen((v) => !v); + }; + + // Escape + outside click close. Focus first menuitem on open. + useEffect(() => { + if (!open) return; + + const focusFirst = () => { + const first = menuRef.current?.querySelector( + '[role="menuitem"]', + ); + first?.focus(); + }; + requestAnimationFrame(focusFirst); + + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + e.stopPropagation(); + closeMenu(); + return; + } + if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { + const items = Array.from( + menuRef.current?.querySelectorAll( + '[role="menuitem"]', + ) ?? [], + ); + if (items.length === 0) return; + const idx = items.indexOf(document.activeElement as HTMLButtonElement); + const next = + e.key === 'ArrowDown' + ? items[(idx + 1) % items.length] + : items[(idx - 1 + items.length) % items.length]; + e.preventDefault(); + next?.focus(); + } + }; + + const onPointerDown = (e: PointerEvent) => { + const target = e.target as Node | null; + if ( + target && + !menuRef.current?.contains(target) && + !triggerRef.current?.contains(target) + ) { + // Outside click: close without stealing focus back to the trigger. + closeMenu(false); + } + }; + + document.addEventListener('keydown', onKey); + document.addEventListener('pointerdown', onPointerDown); + return () => { + document.removeEventListener('keydown', onKey); + document.removeEventListener('pointerdown', onPointerDown); + }; + }, [open, closeMenu]); + + const showMenu = provider === 'both' && open; + const menuVisuallyAvailable = provider === 'both'; + + const buttonContent = ( + <> +